Xavier Nayrac

Rubyiste accro au TDD, serial blogger, apprenti data scientist, heureux utilisateur de Vim, accordéoniste.
Si vous vous sentez particulièrement généreux, suivez moi sur Twitter.

Le hack FizzBuzz en Ruby

| Comments

Niveau : intermédiaire

Voici ce qui trainait sur le net aujourd’hui, un jeu de FizzBuzz en une ligne de Ruby:

1
1.upto(100){|n|puts'FizzBuzz '[o=n**4%-15,o+13]||n}

Ce qui donne:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
ruby -e "1.upto(100){|n|puts'FizzBuzz '[o=n**4%-15,o+13]||n}"
1
2
Fizz
4
Buzz
Fizz
7
8
Fizz
Buzz
11
.
.
.
94
Buzz
Fizz
97
98
Fizz
Buzz

Pour celles et ceux qui ne pigent pas cette ligne de code Ruby, on va la déconstruire petit à petit.

Nous avons affaire à une boucle (j’ai remplacé les {} par do end):

1
2
3
1.upto(100) do |n|
  puts'FizzBuzz '[o=n**4%-15,o+13]||n
end

a.upto(b) itère de a jusqu’à b:

1
2
3
4
5
6
7
$ irb
>> 10.upto(12) do |iteration|
?>   puts iteration
>> end
10
11
12

Regardons de plus près l’intérieur de la boucle, là où c’est intéressant. Ruby permet d’appeler les méthodes sans utiliser de parenthèses. Ici je les ai simplement rajouter pour tenter de clarifier le code:

1
2
3
1.upto(100) do |n|
  puts( 'FizzBuzz '[o=n**4%-15,o+13]||n )
end

Maintenant qu’on est bien sûr qu’il s’agit de la méthode puts avec un argument bizarre dedans, je sépare les différents éléments de la grammaire de Ruby par des espaces. Toujours pour essayer d’y voir plus clair:

1
2
3
1.upto(100) do |n|
  puts( 'FizzBuzz '[o = n ** 4 % -15, o + 13] || n )
end

Arrêtons nous un peu sur les sous-ensembles de chaîne [a, b]. Le premier chiffre est la position de départ, le second est le nombre de caractères:

1
2
3
4
5
$ irb
>> "abcdef"[0, 2]
"ab"
>> "abcdef"[3, 2]
"de"

Ruby permet de donner une position de départ pas rapport à la fin:

1
2
>> "abcdef"[-4, 2]
"cd"

Pour la suite, notez bien que si on va chercher des caractères avant le début ou après la fin de la chaîne, on se retrouve avec nil. Ça n’est pas une erreur, c’est le comportement attendu:

1
2
3
4
>> "abcdef"[-123, 2]
nil
>> "abcdef"[999, 2]
nil

Maintenant essayons de décrypter la formule mathématique. Utilisons une variable temporaire pour l’isoler:

1
2
3
4
1.upto(100) do |n|
  o = n ** 4 % -15
  puts( 'FizzBuzz '[o, o + 13] || n )
end

En Ruby, ** est l’opérateur de puissance:

1
2
3
4
5
6
7
$ irb
>> 10 ** 2
100
>> 10 ** 3
1000
>> 10 ** 4
10000

Quant à %, c’est l’opérateur de la division modulaire, souvent appelé modulo. Si vous avez plein de temps devant vous il y a la page wikipédia sur l’arithmétique modulaire. Sinon voici une explication rapide du modulo: c’est le reste de la division entière. Donc 9 % 4 == 1.

Sauf qu’ici on utilise le modulo avec un nombre négatif. Et là les maths se compliquent un peu, et surtout différents langages de programmation auront différents comportements.

Essayons plutôt de sentir la formule en l’appliquant aux nombres de 1 à 16:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
$ irb
>> (1..16).each do |n|
?>   o = n ** 4 % -15
>>   puts "#{n} => #{o}"
>> end
1 => -14
2 => -14
3 => -9
4 => -14
5 => -5
6 => -9
7 => -14
8 => -14
9 => -9
10 => -5
11 => -14
12 => -9
13 => -14
14 => -14
15 => 0
16 => -14

J’imagine qu’à ce moment précis, vous êtes plusieurs à crier «Ha ha !».

Cette formule, en Ruby, a donc 4 solutions possibles:

  • Quand n est un multiple de 3, la solution est -9.
  • Quand n est un multiple de 5, la solution est -5.
  • Quand n est un multiple de 15 (c’est à dire à la fois multiple de 3 et de 9), la solution est 0.
  • Dans les autres cas, la solution est -14.

Voyons ce qu’il se passe avec 'FizzBuzz ' quand o vaut respectivement -9, -5, 0 puis -14:

1
2
3
4
5
6
7
8
9
$ irb
>> 'FizzBuzz '[-9, -9 + 13]
"Fizz"
>> 'FizzBuzz '[-5, -5 + 13]
"Buzz "
>> 'FizzBuzz '[0, 13]
"FizzBuzz "
>> 'FizzBuzz '[-14, -14 + 13]
nil

Peut-être vous demandez vous pourquoi il y a un espace après FizzBuzz ? En pratique, voici la raison:

1
2
3
4
>> 'FizzBuzz'[-9, -9 + 13]
nil
>> 'FizzBuzz'[-5, -5 + 13]
"zBuzz"

En théorie, je vous laisse faire les calculs ;)

Pour finir, il reste à expliquer le ||, un exemple vaut mieux qu’un long discours:

1
2
3
4
>> "foo" || 13
"foo"
>> nil || 13
13

Voilà, si vous voulez ajouter d’autres explications, les commentaires sont fait pour ça ;)

Articles connexes

Commentaires