web-dev-qa-db-fra.com

Pourquoi RuboCop suggère-t-il de remplacer .times.map par Array.new?

RuboCop suggère:

Utilisation Array.new avec un bloc au lieu de .times.map.

Dans le docs pour le flic:

Ce flic recherche les appels .times.map. Dans la plupart des cas, ces appels peuvent être remplacés par une création de tableau explicite.

Exemples:

# bad
9.times.map do |i|
  i.to_s
end

# good
Array.new(9) do |i|
  i.to_s
end

Je sais qu'il peut être remplacé, mais je pense que 9.times.map est plus proche de la grammaire anglaise, et il est plus facile de comprendre ce que fait le code.

Pourquoi devrait-il être remplacé?

36
shirakia

Ce dernier est plus performant; voici une explication: demande Pull où ce flic a été ajouté

Il vérifie les appels comme celui-ci:

9.times.map { |i| f(i) }
9.times.collect(&foo)

et suggère d'utiliser ceci à la place:

Array.new(9) { |i| f(i) }
Array.new(9, &foo)

Le nouveau code a approximativement la même taille, mais utilise moins d'appels de méthode, consomme moins de mémoire, fonctionne un tout petit peu plus vite et à mon avis est plus lisible.

J'ai vu de nombreuses occurrences de fois. {Map, collect} dans différents projets bien connus: Rails, GitLab, Rubocop et plusieurs applications de source fermée.

Repères:

Benchmark.ips do |x|
  x.report('times.map') { 5.times.map{} }
  x.report('Array.new') { Array.new(5){} }
  x.compare!
end
__END__
Calculating -------------------------------------
           times.map    21.188k i/100ms
           Array.new    30.449k i/100ms
-------------------------------------------------
           times.map    311.613k (± 3.5%) i/s -      1.568M
           Array.new    590.374k (± 1.2%) i/s -      2.954M

Comparison:
           Array.new:   590373.6 i/s
           times.map:   311612.8 i/s - 1.89x slower

Je ne suis pas sûr maintenant que Lint soit l'espace de noms correct pour le flic. Dites-moi si je dois le déplacer vers Performance.

De plus, je n'ai pas implémenté la correction automatique car cela peut potentiellement casser le code existant, par exemple si quelqu'un a la méthode Fixnum # times redéfinie pour faire quelque chose d'extraordinaire. L'application de la correction automatique briserait leur code.

35
MikDiet

Si vous pensez que c'est plus lisible, allez-y.

Il s'agit d'une règle de performances et la plupart des chemins de code de votre application ne sont probablement pas critiques en termes de performances. Personnellement, je suis toujours ouvert à privilégier la lisibilité à l'optimisation prématurée.

Cela dit

100.times.map { ... }
  • times crée un objet Enumerator
  • map énumère cet objet sans pouvoir l'optimiser, par exemple la taille du tableau n'est pas connue à l'avance et il peut être nécessaire de réallouer dynamiquement plus d'espace et il doit énumérer les valeurs en appelant Enumerable#each puisque map est implémenté de cette façon

Tandis que

Array.new(100) { ... }
  • new alloue un tableau de taille N
  • Et puis utilise une boucle native pour remplir les valeurs
12
akuhn

Lorsque vous devez mapper le résultat d'un bloc invoqué un nombre fixe de fois, vous avez le choix entre:

Array.new(n) { ... }

et:

n.times.map { ... }

Ce dernier est environ 60% plus lent pour n = 10, qui descend à environ 40% pour n > 1_000.

Remarque: échelle logarithmique!

enter image description here

5
Drenmi