128 lines
4.6 KiB
Markdown
128 lines
4.6 KiB
Markdown
|
# AoC 2021 Jour 1: Sonar Sweep
|
||
|
|
||
|
Comment piloter le sous-marin des elfes ?
|
||
|
|
||
|
Le défi peut être trouvé [ici](https://adventofcode.com/2021/day/2), et le code
|
||
|
lié est sur [gitlab](https://gitlab.com/lovallat/advent-of-code-2021/-/tree/master/day2).
|
||
|
|
||
|
## Consigne du défi
|
||
|
|
||
|
Le sous-marin peut prendre 3 types de commandes :
|
||
|
|
||
|
- `forward X`
|
||
|
- `down X`
|
||
|
- `up X`
|
||
|
|
||
|
Le fichier d'entrée est une suite de ces ordres, que l'on doit interpréter pour
|
||
|
trouver la position du sous-marin dans l'espace (horizontalement et verticalement).
|
||
|
|
||
|
## Lecture du fichier d'entrée
|
||
|
|
||
|
La lecture du fichier d'entrée se fait à nouveau dans une seule fonction :
|
||
|
|
||
|
```rust
|
||
|
fn parse_input(s: &str) -> Vec<(String, i32)> {
|
||
|
let mut r:Vec<(String, i32)> = vec![];
|
||
|
let v = s.split("\n").into_iter();
|
||
|
for e in v {
|
||
|
let p = e.split_whitespace().collect::<Vec<&str>>();
|
||
|
if p.len() == 2 && p[1].parse::<i32>().is_ok() {
|
||
|
r.push((p[0].to_owned(), p[1].parse::<i32>().unwrap()));
|
||
|
}
|
||
|
}
|
||
|
return r;
|
||
|
}
|
||
|
```
|
||
|
|
||
|
Ici, on va tout simplement transformer une ligne en tuple `(String, i32)`, et
|
||
|
de transformer le fichier d'entrée en un tableau `Vec` de ces ordres. On découpe
|
||
|
donc une ligne `ordre X` en fonction d'un espace, et si on a bien 2 éléments et
|
||
|
que le deuxième peut être converti en entier signé stocké sur 32 bits, alors on
|
||
|
l'ajoute à notre tableau d'instructions.
|
||
|
|
||
|
On pourrait ici utiliser une énumeration telle que `Direction::Forward`,
|
||
|
`Direction::Up` et `Direction::Down` pour éviter de stocker les chaînes de
|
||
|
caractère en entier dans le tableau. On pourrait aussi utiliser des entiers stockés
|
||
|
sur moins de bits, étant relativement petits.
|
||
|
|
||
|
## Première partie
|
||
|
|
||
|
Pour la première partie, l'interprétation des instructions est la suivante :
|
||
|
|
||
|
- `forward X` qui permet de faire avancer le sous-marin de `X` unités horizontalement
|
||
|
- `down X` fait plonger le sous-marin et donc augmente sa profondeur de `X` unités
|
||
|
- `up X` fait remonter à l'inverse le sous-marin vers la surface
|
||
|
|
||
|
La réponse à la première partie se trouve en multipliant la position horizontale
|
||
|
et verticale. Pour cela, on va faire un `fold` sur le tableau pour calculer toutes
|
||
|
les instructions à la suite et donnant le résultat en une seule ligne :
|
||
|
|
||
|
```rust
|
||
|
fn get_horizontal_depth(v: Vec<(String, i32)>) -> (i32, i32) {
|
||
|
return v.into_iter().fold((0, 0), |acc, e| compute_horizontal_depth(e, acc));
|
||
|
}
|
||
|
```
|
||
|
|
||
|
Chaque ligne est traîtée individuellement pour interprétation, en donnat en valeur
|
||
|
de retour les nouvelles positions du sous-marin après application de l'instruction
|
||
|
sur la position donnée en entrée :
|
||
|
|
||
|
```rust
|
||
|
fn compute_horizontal_depth(t: (String, i32), a: (i32, i32)) -> (i32, i32) {
|
||
|
return match t.0.as_str() {
|
||
|
"up" => (a.0, a.1 - t.1),
|
||
|
"down" => (a.0, a.1 + t.1),
|
||
|
"forward" => (a.0 + t.1, a.1),
|
||
|
_ => a
|
||
|
};
|
||
|
}
|
||
|
```
|
||
|
|
||
|
Ce traîtement est donc très simple. Pour chaque instruction et en partant d'une
|
||
|
position `(0, 0)`, on va appliquer les règles les unes après les autres en mettant
|
||
|
à jour la position à chaque fois. Cela se fait en temps linéaire sans calcul autre
|
||
|
que des additions.
|
||
|
|
||
|
## Deuxième partie
|
||
|
|
||
|
Pour la deuxième partie, le traîtement est assez similaire, mais on va calculer
|
||
|
une composante supplémentaire : la *visée*.
|
||
|
|
||
|
Les instructions sont désormais à interpréter de cette manière :
|
||
|
|
||
|
- `forward X` permet deux choses :
|
||
|
- augmente la position horizontale de `X` unités
|
||
|
- augmente la profondeur par la *visée* multipliée par `X`
|
||
|
- `down X` augmente la *visée* de `X` unités
|
||
|
- `up X` à l'inverse diminue la *visée* de `X` unités
|
||
|
|
||
|
Le concept pour calculer est le même, on applique les instructions l'une après
|
||
|
l'autre en partant de `(0, 0, 0)`.
|
||
|
|
||
|
```rust
|
||
|
fn compute_horizontal_depth_aim(t: (String, i32), a: (i32, i32, i32)) -> (i32, i32, i32) {
|
||
|
return match t.0.as_str() {
|
||
|
"up" => (a.0, a.1, a.2 - t.1),
|
||
|
"down" => (a.0, a.1, a.2 + t.1),
|
||
|
"forward" => (a.0 + t.1, a.1 + a.2 * t.1, a.2),
|
||
|
_ => a
|
||
|
};
|
||
|
}
|
||
|
```
|
||
|
|
||
|
Enfin, la réponse à donner est la multiplication de la position horizontale et
|
||
|
verticale, comme pour la précédente partie.
|
||
|
|
||
|
## Conclusion
|
||
|
|
||
|
Ce défi n'était pas compliqué et ma réponse me semble adaptée. Bien sûr il y a
|
||
|
toujours des pistes d'améliorations, telle que le type de données stockées :
|
||
|
les chaînes de caractères étant moins performantes à traîter, mais aussi la taille
|
||
|
des entiers stockés, ou encore le fait que je ne passe pas de référence aux
|
||
|
fonctions, nécéssitant une copie systématique, consommant des cycles supplémentaires.
|
||
|
|
||
|
Mais globalement je suis satisfait de ma réponse à ce défi.
|
||
|
|
||
|
> À suivre
|
||
|
|