A l’occasion du concours du Meilleur Développeur de France 2014, le site de l’événement permettait de se familiariser avec l’interface en proposant un puzzle d’entrainement. Dans ce puzzle, le système choisi deux entiers « n » et « p » non multiples. Le candidat reçoit une String contenant les chiffres de 1 à 100 séparés par des espaces, où les multiples de « n » et de « p » sont remplacés par le mot « Buzz ». Voici donc une proposition de réponse.
Dans la suite, je vais vous épargner les détails propres à l’interface du concours. Donc on reçoit une String avec des chiffres et des mots. Dans un premier temps, je me suis contenté d’afficher cette string, histoire de voir à quoi elle ressemble :
1 | 1 2 3 4 5 6 7 8 Buzz 10 11 12 13 14 15 16 17 Buzz 19 20 21 22 23 24 25 26 Buzz 28 29 30 31 32 33 34 35 Buzz 37 38 39 40 41 42 43 44 Buzz 46 47 48 49 50 51 52 53 Buzz 55 56 57 58 59 60 61 62 Buzz 64 65 66 67 68 69 70 71 Buzz 73 74 75 76 77 78 79 80 Buzz 82 83 84 85 86 87 88 89 Buzz Buzz 92 93 94 95 96 97 98 Buzz 100 |
A l’œil nu, on devine que la bonne réponse sera le couple (n, p) : (9, 91). On ne peut pas prendre 18 ou 72 (etc.) comme valeur de « p » car ce seraient des multiples de « n ».
Il faut donner la réponse dans la console. Pour les besoins de ce billet, on dira qu’on la donne sous forme de string : 9 91.
Si on lance plusieurs fois le test, ça nous donne plusieurs strings :
1 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 Buzz 20 21 22 23 24 25 26 27 28 29 30 31 32 Buzz 34 35 36 37 Buzz 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 Buzz 58 59 60 61 62 63 64 65 Buzz 67 68 69 70 71 72 73 74 75 Buzz 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 Buzz 96 97 98 Buzz 100 |
1 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 Buzz 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 Buzz 100 |
Ces trois strings vont me servir de jeux de test pour la suite puisque, comme vous l’avez surement déjà deviné, je vais utiliser 3T (Tests en Trois Temps) pour programmer ma réponse.
Je commence donc par créer une classe qui définit le contrat de service, qui compile, mais qui ne fait rien :
1 2 3 4 5 6 7 8 9 | package com.icauda.article.buzz; public class BuzzSolver { public static String solve(final String line) { throw new UnsupportedOperationException("not yet"); } } |
Je vais maintenant faire des tests. Je vous épargne les étapes intermédiaires :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 | package com.icauda.article.buzz; import org.junit.Assert; import org.junit.Test; public class BuzzSolverTest { @Test public void testLineSolve9_91() { // Arrange final String line = "1 2 3 4 5 6 7 8 Buzz 10 11 12 13 14 15 16 17 Buzz 19 20 21 22 23 24 25 26 Buzz 28 29 30 31 32 33 34 35 Buzz 37 38 39 40 41 42 43 44 Buzz 46 47 48 49 50 51 52 53 Buzz 55 56 57 58 59 60 61 62 Buzz 64 65 66 67 68 69 70 71 Buzz 73 74 75 76 77 78 79 80 Buzz 82 83 84 85 86 87 88 89 Buzz Buzz 92 93 94 95 96 97 98 Buzz 100"; final String expected = "9 91"; // Act and Assert doTestSolve(line, expected); } @Test public void testLineSolve19_33() { // Arrange final String line = "1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 Buzz 20 21 22 23 24 25 26 27 28 29 30 31 32 Buzz 34 35 36 37 Buzz 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 Buzz 58 59 60 61 62 63 64 65 Buzz 67 68 69 70 71 72 73 74 75 Buzz 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 Buzz 96 97 98 Buzz 100"; final String expected = "19 33"; // Act and Assert doTestSolve(line, expected); } @Test public void testLineSolve78_99() { // Arrange final String line = "1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 Buzz 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 Buzz 100 "; final String expected = "78 99"; // Act and Assert doTestSolve(line, expected); } private static void doTestSolve(final String line, final String expected) { // Act final String result = BuzzSolver.solve(line); // Assert Assert.assertEquals(expected, result); } } |
Je lance mes tests et je vois qu’ils sont tous rouges, ce qui est précisément ce que je souhaite à cette étape.
Et il ne reste plus qu’à répondre à l’exercice. Comme le but du concours est de programmer du code jetable le plus vite possible, je vais faire simple…
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
Vous remarquerez que je fais un "break" dans le "else" pour provoquer une sortie rapide et ne pas écraser mes valeurs.
Je relance mes tests et je constate qu'il y en a déjà un qui est passé au vert. Il en reste deux. Il faut surtout que je fasse attention aux multiples :
1 2 3 4 5 6 7 8 9 10 11 12 13 | for (int i = 0; i < tab.length; i++) { if (tab[i].equals("Buzz")) { final int valeur = i + 1; if (n == 0) { n = valeur; } else if (valeur % n == 0) { continue; } else { p = valeur; break; } } } |
Je relance et je constate avec plaisir que tout est vert. Je suis donc légitimement en droit de penser que j'ai terminé. En vrai je vois bien que je pourrais en faire plus mais je vais m'en contenter pour le concours…
Je suis d’accord avec Thierry sur le côté très verbeux de la solution proposée avec Java 8.
Cependant, la programmation fonctionnelle peut tout de même nous aider.
Voyez ci-après ma tentative en Scala:
2
3
4
val buzzIndices = input.split(" ").zipWithIndex.filter(_._1.equals("Buzz")).map(_._2 + 1)
(buzzIndices.head, buzzIndices.tail.find(_ % buzzIndices.head != 0).get)
}
La méthode zipWithIndex permet de construire un tuple à partir de chaque élément et son index dans le tableau.
Ex: (« 1″, 0), (« Buzz », 1).
On filtre ensuite ces tuples pour ne garder que ceux ayant comme premier élément « Buzz ». Enfin, on ne conserve que les indexes de ces derniers que l’on incrémente de 1.
Le premier de ces indexes est forcément n; donc la tête du tableau.
Il suffit ensuite de prendre le premier élément qui n’est pas multiple du premier.
Juste pour le plaisir, j’ai essayé d’écrire la méthode « solve » en utilisant les nouvelles API Java 8 :
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
AtomicInteger compteur = new AtomicInteger(0);
return Arrays.stream(line.split(" "))
.map(word -> new Pair(compteur.incrementAndGet(), word))
.filter(wordWithIndex -> wordWithIndex.getValue().equalsIgnoreCase("buzz"))
.map(Pair::getKey)
.collect(
ArrayList::new,
(list, index) -> {
if (list.isEmpty()) {
list.add(index);
} else if (list.size() < 2) {
if (index % list.get(0) != 0) {
list.add(index);
}
}
},
ArrayList::addAll
).stream()
.map(Object::toString).collect(
Collectors.joining(" ")
);
}
Si quelqu'un a plus simple, je suis preneur
Je ne suis pas encore passé à Java 8 donc je ne peux pas vérifier si ça fonctionne. En tous cas, ça a l’air vachement plus complexe (et tordu aussi) que ma version… Comme quoi, la programmation fonctionnelle n’est pas forcément gage de lisibilité.