Le site arthur.bebou.netlib.re - retour accueil
git clone git://bebou.netlib.re/arthur.bebou
Log | Files | Refs |
index.sh (9962B)
1 #! page 2 title: Advent of code 2024^\(mais c\'est l\'advent of *unix* code\) 3 author: Arthur Pons 4 description: L\'advent of code 2024 version Unix 5 publication: 2024-12-04 6 7 sectionmd: main 8 9 ## Qu'est-ce que c'est ? 10 11 L'[advent of code](https://adeventofcode.com/2024/about) est un évènement 12 annuel créé par [Eric Wastl](https://was.tl/projects/)[^1] durant lequel 13 il faut résoudre des exercices algorithmiques. Je documente ici mes 14 *tentatives* en utilisant les outils traditionnels d'Unix (les commandes 15 posix, awk, sed, peut-être un peu de GNU coreutils etc). 16 17 Je ne pense pas faire les 25 jours. Premièrement ça demande d'être motivé sur 18 une longue période ce que j'ai du mal à faire, deuxièmement cette boite à outil 19 est mal adapté à de nombreux types de problèmes généralement posés. 20 21 Mon but est, pour chaque jour que je vais faire, de mettre en lumière une 22 fonctionnalité méconnue, problème embêtant, astuce maline etc. 23 24 Malheureusement le jeu nécessite un compte Github, Google, Twitter ou Reddit 25 pour jouer. Je sacrifie donc mon compte Github pour vous fournir l'intitulé du 26 problème de chaque journée. L'entrée du problème (et ma solution) sera dans le 27 [dépôt git aoc2024](http://git.bebou.netlib.re/aoc2024/log.html). Il ce peut 28 que ce qui se trouve commenté dans les articles ne soit plus synchro avec ce 29 qui se trouve dans le dépôt. 30 31 ## Jour 1 32 33 [Jour 2](2.html) 34 35 ### Intitulé résumé 36 37 #### Première partie 38 39 3 4 40 4 3 41 2 5 42 1 3 43 3 9 44 3 3 45 46 Maybe the lists are only off by a small amount! To find out, pair up the 47 numbers and measure how far apart they are. Pair up the smallest number in the 48 left list with the smallest number in the right list, then the second-smallest 49 left number with the second-smallest right number, and so on. 50 51 Within each pair, figure out how far apart the two numbers are; you'll need to 52 add up all of those distances. For example, if you pair up a 3 from the left 53 list with a 7 from the right list, the distance apart is 4; if you pair up a 9 54 with a 3, the distance apart is 6. 55 56 To find the total distance between the left list and the right list, add up the 57 distances between all of the pairs you found. In the example above, this is 2 + 58 1 + 0 + 1 + 2 + 5, a total distance of 11! 59 60 #### Deuxième partie 61 62 This time, you'll need to figure out exactly how often each number from the 63 left list appears in the right list. Calculate a total similarity score by 64 adding up each number in the left list after multiplying it by the number of 65 times that number appears in the right list. 66 67 So, for these example lists, the similarity score at the end of this process is 68 31 (9 + 4 + 0 + 0 + 9 + 9). 69 70 ### Commentaire de ma solution 71 72 #### Première partie 73 74 On veut tout de suite séparer les deux colonnes pour les trier. Il est 75 étrangement "compliqué" de faire ça sur un fichier dont les colonnes son 76 séparées par trois espaces sans dégainer `awk`. En effet, `cut` ne prend qu'un 77 seul caractère comme argument et si on lui donne un espace le fichier sera en 78 réalité séparé en cinq colonnes et non deux comme on voudrait. On peut faire 79 avec mais ce n'est pas agréable. Pour utiliser `cut` on peut masser le fichier 80 en "squeezant" les espaces successifs avec `tr -s ' '` puis un cut. 81 82 Personnellement plutôt que de multiplier les pipes j'ai tendance à dégaîner `awk` 83 dès que je rencontre un fichier dont les champs sont séparés de manière un peu 84 arbitraire avec du blanc. `awk` se charge tout seul de faire le taf et on 85 peut simplement écrire : 86 87 < 1.input awk '{print $1}' | sort 88 < 1.input awk '{print $2}' | sort 89 90 Sinon pour le coeur de la réponse j'ai eu naturellement recours à la technique 91 "utiliser sed,paste,tr pour écrire des maths et piper tout ça dans `bc`". C'est 92 une illustration de se dont parle [Florian Cramer](http://floriancramer.nl/) 93 dans son super article [$(echo echo) echo 94 $(echo)](http://reader.lgru.net/texts/echo-echo-echo-echo-command-line-poetics/). 95 96 > Command lines can be constructed that wrangle input data, text, into new 97 > commands to be executed. Unlike in GUIs, there is recursion in user interfaces: 98 > commands can process themselves. 99 100 J'utilise également un fifo pour éviter de créer un fichier temporaire qui 101 risquerait de rester ici et de prendre de la place sur mon disque. Si l'on ne 102 prend pas le temps de supprimer le fifo il restera également mais au moins il 103 est vide. 104 105 Au final ça donne : 106 107 mkfifo left 2> /dev/null 108 < 1.input awk '{print $1}' | sort > left& 109 < 1.input awk '{print $2}' | sort | 110 paste -d '-' left - | grep -Ev '^-$' | 111 bc | 112 tr -d '-' | paste -s -d'+' | 113 bc 114 115 Avec cette idée de manipuler du texte pour créer des formules mathématiques 116 l'équivalent de `abs(x)` devient `tr -d '-'`. 117 118 #### Deuxième partie 119 120 Ce problème est une bonne opportunité pour évoquer le shell et ses performances. 121 Récemment on m'a demandé s'il était possible d'implémenter des algos de machine 122 learning (spécifiquement avec des réseaux de neurones) en shell. La réponse est 123 techniquement oui mais il serait vain de tenter de le faire, le shell n'est pas 124 fait pour faire de l'algèbre. Cela a ouvert une discussion plus générale sur 125 la performance du shell. 126 127 On admet ici qu'il n'est pas question des performances du shell "pure" qui 128 n'appellerait aucun programme externe. Bien qu'il soit possible de programmer 129 en shell "pure" il n'existe pas vraiment de bonne raison de le faire. 130 peut s'installer sur un unix". A l'inverse on ne compterait pas comme "du 131 shell" un script qui se contenterait d'appeler un autre programme bien que ce 132 soit un usage légitime. Dans presque tous les cas quand on parle du shell on 133 entend "un programme shell unixien pouvant appeler à minima les outils posix et 134 au maxima n'importe quel outil qui peut s'installer sur un Unix et dont le 135 fonctionnement et le temps d'exécution dépend pour une partie non négligeable 136 des méchanismes du shell lui même (pipes, boucles, redirections etc)." 137 138 Aujourd'hui je répond à cette question de cette manière : 139 140 1. Le shell "pure" n'est pas un langage très performant 141 2. *mais* il permet d'orchestrer efficacement des programmes qui peuvent être 142 très performants 143 3. la performance d'un programme en shell dépend donc, peut-être plus que 144 pour n'importe quel autre langage, des outils utilisés **et** de la manière 145 de les agencer. 146 147 La nature des shell unix fait qu'il est très facile de d'utiliser des 148 mécanismes assez avancés permettant d'accélérer l'exécution (parallélisme avec 149 le pipe) et tout aussi facile de faire de grosses bêtises (forker 150 exponentiellement en fonction de la taille de notre problème). L'étendu des 151 performances possibles en shell pour un problème donné est très grande. 152 153 Illustrons cet exemple avec notre deuxième partie. Une solution qui pourrait 154 paraître naturelle, notamment pour une personne ayant l'habitude d'un langage 155 comme le C, serait : 156 157 < 1.input awk '{print $2}' | sort | uniq -c > rightcount 158 for e in $(< 1.input tr -s ' ' | cut -f1 -d' ');do 159 echo "$e * $(grep -E "$e$" rightcount | awk '{print $1}')" 160 done | sed 's/* $/*0/' | bc | paste -s -d'+' | bc 161 162 Dans `rightcount` le nombre d'occurences des chiffres dans la seconde colonnes 163 du type :`3 1234` si `1234` y apparaît 3 fois. Pour chaque élément de la 164 première colonne on va récupérer ses occurrences dans `rightcount` qu'on multiplie 165 à sa propre valeur. A la fin de la boucle on fait la somme et hop dans bc. 166 167 C'est rapide à écrire mais c'est leeent. Pourquoi ? Parce que pour chaque 168 nombre (et y'en a pas mal) on fork pour exécuter notre `grep` et aller chercher 169 la bonne ligne dans `rightcount`. De manière générale quand on fork beaucoup 170 (par exemple pleins de captures de commandes) dans une boucle en shell c'est 171 mauvais signe pour les perfs. Bien sûr si votre problème est petit ce n'est pas 172 un souci. A vous de voir si la relative lisibilité du code vaut le coup de perf[^2]. 173 174 Une solution plus "unixienne", beaucoup plus performante mais aussi plus obscure quand on y est pas habitué serait : 175 176 < 1.input awk '{print $2}' | sort | uniq -c > rightcount 177 < 1.input awk '{print $1}' | sort > leftc 178 < rightcount grep -f leftc | awk '{print $1"*"$2}' | paste -s -d'+' | bc 179 180 Toujours nos occurences dans `rightcount`, Dans `leftc` la première colonne 181 triée. Ici l'astuce est de laisser `grep` faire le travail en utilisant son 182 option `-f` qui permet d'utiliser comme pattern à matcher les lignes successives 183 d'un fichier. Il se trouve que c'est exactement ce que l'on veut faire ! Plus 184 qu'à créer les formules et hop dans `bc`. 185 186 C'est performant parce que le très gros du travail est fait dans un seul 187 processus `grep`. Et `grep`[^3], c'est trèèèès rapide. 188 189 Dans l'ensemble le shell est très bon quand : 190 191 1. le gros du travail peut être sous traité à un coreutils 192 2. il est possible de paralléliser le problème par ligne (le pipe est alors 193 un gain de perf "gratuit") 194 3. le problème n'est pas complexe algorithmiquement parlant, il y a peu ou 195 pas d'exceptions, peu de finesse dans le traitement à faire, peu 196 d'embranchements logiques, peu de maths 197 198 Pour plus de contenu sur le shell, son monde et ses perfs voir [un article sur 199 sed](/substitutions/) ou [cet article assez connu qui compare le shell avec un 200 cluster 201 hadoop](https://adamdrake.com/command-line-tools-can-be-235x-faster-than-your-hadoop-cluster.html). 202 203 [^1]: également créateur de vanilla-js.com donc une personne particulièrement drôle. 204 [^2]: au passage il y a une autre bêtise ici qui est que la formule peut être construite uniquement avec les données qui sortent du `grep` en faisant `awk '{print $1*$2}'\ mais peu importe, ça ne changerait presque rien en terme de perf. De manière générale le fait que beaucoup de choses peuvent être mises dans awk et possiblement pour le meilleur sera, je le prédis, un thème récurrent de l'aoc. 205 [^3]: en particulier GNU grep