Quelle est la "bonne" façon d'itérer à travers un tableau dans Ruby?

PHP, pour toutes ses verrues, est assez bon sur ce compte. Il n'y a pas de différence entre un tableau et un hachage (peut-être que je suis naïf, mais cela me semble évidemment juste), et pour parcourir soit vous faites juste

foreach (array/hash as $key => $value)

Dans Ruby, il y a un tas de façons de faire ce genre de chose:

array.length.times do |i|
end

array.each

array.each_index

for i in array

Les hachages ont plus de sens, puisque j'utilise toujours

hash.each do |key, value|

Pourquoi ne puis-je pas le faire pour les tableaux? Si je veux me souvenir d'une seule méthode, je suppose que je peux utiliser each_index (car il fait les deux l'index et la valeur disponible), mais c'est ennuyeux d'avoir à faire array[index] au lieu de simplement value.


Ah oui, j'ai oublié array.each_with_index. Cependant, celui-ci craint parce qu'il va |value, key| et hash.each VA |key, value|! N'est-ce pas fou?

299
demandé sur Andrew Grimm 2008-11-22 03:38:19

11 réponses

Cela parcourra tous les éléments:

array = [1, 2, 3, 4, 5, 6]
array.each { |x| puts x }

Impressions:

1
2
3
4
5
6

Cela parcourra tous les éléments vous donnant la valeur et l'index:

array = ["A", "B", "C"]
array.each_with_index {|val, index| puts "#{val} => #{index}" }

Impressions:

A => 0
B => 1
C => 2

Je ne suis pas tout à fait sûr de votre question que vous cherchez.

499
répondu Robert Gamble 2012-07-13 15:53:18

Je pense qu'il n'y a personne Droit façon. Il y a beaucoup de façons différentes d'itérer, et chacun a sa propre niche.

  • {[0] } est suffisant pour de nombreux usages, car je ne me soucie pas souvent des index.
  • each_ with _index agit comme Hash # each - vous obtenez la valeur et l'index.
  • each_index - juste les index. Je n'ai pas l'utiliser souvent. Équivalent à " longueur.temps".
  • map est une autre façon d'itérer, utile lorsque vous voulez transformer un tableau en l'autre.
  • select est l'itérateur à utiliser lorsque vous voulez choisir un sous-ensemble.
  • {[5] } est utile pour générer des sommes ou des produits, ou collecter un seul résultat.

Cela peut sembler beaucoup à retenir, mais ne vous inquiétez pas, vous pouvez vous en sortir sans les connaître tous. Mais comme vous commencez à apprendre et à utiliser les différentes méthodes, votre code deviendra plus propre et plus clair, et vous serez sur votre chemin vers la maîtrise de Ruby.

70
répondu AShelly 2016-02-18 20:29:58

Je ne dis pas ça Array -> |value,index| et Hash -> |key,value| n'est pas fou (voir le commentaire D'Horace Loeb), mais je dis qu'il y a une façon saine d'attendre cet arrangement.

Quand j'ai affaire à des tableaux, je me concentre sur les éléments du tableau (pas sur l'index car l'index est transitoire). La méthode est chaque avec index, c'est à dire chaque+index, ou |chaque,index| ou |value,index|. Ceci est également cohérent avec le fait que l'index est considéré comme un argument optionnel, par exemple / value / est équivalent à |valeur,indice=nil| ce qui est cohérent avec |valeur d'indice.

Quand je traite des hachages, je suis souvent plus concentré sur les clés que sur les valeurs, et je traite généralement des clés et des valeurs dans cet ordre, soit key => value ou hash[key] = value.

Si vous voulez taper duck, utilisez soit explicitement une méthode définie comme l'a montré Brent Longborough, soit une méthode implicite comme l'a montré maxhawkins.

Ruby est tout au sujet d'accommoder le langage pour convenir au programmeur, pas sur le programmeur accueillir fonction de la langue. C'est pourquoi il existe de nombreuses façons. Il y a tellement de façons de penser à quelque chose. Dans Ruby, vous choisissez le plus proche et le reste du code tombe généralement extrêmement soigneusement et de manière concise.

Quant à la question originale, "Quelle est la "bonne" façon d'itérer à travers un tableau dans Ruby?", Eh bien, je pense que la manière de base (c'est-à-dire sans sucre syntaxique puissant ou puissance orientée objet) est de faire:

for index in 0 ... array.size
  puts "array[#{index}] = #{array[index].inspect}"
end

Mais Ruby est tout au sujet sucre syntaxique puissant et puissance orientée objet, mais de toute façon voici l'équivalent pour les hachages, et les clés peuvent être commandées ou non:

for key in hash.keys.sort
  puts "hash[#{key.inspect}] = #{hash[key].inspect}"
end

Donc, ma réponse est, " la "bonne" façon d'itérer à travers un tableau dans Ruby dépend de vous (c'est-à-dire le programmeur ou l'équipe de programmation) et le projet.". Le meilleur programmeur Ruby fait le meilleur choix (dont la puissance syntaxique et / ou quelle approche orientée objet). Le meilleur programmeur Ruby continue de chercher plus façon.


Maintenant, je veux poser une autre question, " Quelle est la "bonne" façon d'itérer à travers une plage dans Ruby en arrière?"! (Cette question Est de savoir comment je suis arrivé à cette page.)

C'est agréable à faire (pour les avants):

(1..10).each{|i| puts "i=#{i}" }

, Mais je n'aime pas faire (pour l'arrière):

(1..10).to_a.reverse.each{|i| puts "i=#{i}" }

Eh bien, cela ne me dérange pas trop, mais quand j'enseigne en arrière, je veux montrer à mes élèves une belle symétrie (c'est-à-dire avec une différence minimale, par exemple en ajoutant seulement un inverse, ou une étape -1, mais sans modifier quoi que ce soit d'autre). Vous pouvez faire (pour la symétrie):

(a=*1..10).each{|i| puts "i=#{i}" }

Et

(a=*1..10).reverse.each{|i| puts "i=#{i}" }

Que je n'aime pas beaucoup, mais vous ne pouvez pas faire

(*1..10).each{|i| puts "i=#{i}" }
(*1..10).reverse.each{|i| puts "i=#{i}" }
#
(1..10).step(1){|i| puts "i=#{i}" }
(1..10).step(-1){|i| puts "i=#{i}" }
#
(1..10).each{|i| puts "i=#{i}" }
(10..1).each{|i| puts "i=#{i}" }   # I don't want this though.  It's dangerous

Vous pourriez finalement faire

class Range

  def each_reverse(&block)
    self.to_a.reverse.each(&block)
  end

end

Mais je veux enseigner Ruby pur plutôt que des approches orientées objet (pour l'instant). Je voudrais itérer en arrière:

  • sans créer de tableau (considérons 0..1000000000)
  • Fonctionne pour n'importe quelle plage (par exemple des Chaînes, pas seulement Entiers)
  • sans utiliser de puissance orientée objet supplémentaire (c'est-à-dire sans modification de classe)

Je crois que c'est impossible sans définir une méthode pred, ce qui signifie modifier la classe Range pour l'utiliser. Si vous pouvez le faire, faites-le moi savoir, sinon la confirmation de l'impossibilité serait appréciée, mais ce serait décevant. Peut-être que Ruby 1.9 aborde cela.

(Merci pour votre temps à lire ceci.)

55
répondu 2009-03-27 23:25:21

Utilisez each_with_index lorsque vous avez besoin des deux.

ary.each_with_index { |val, idx| # ...
15
répondu J Cooper 2008-11-22 00:44:06

Les autres réponses sont très bien, mais je voulais souligner une autre chose périphérique: les tableaux sont ordonnés, alors que les hachages ne sont pas dans 1.8. (Dans Ruby 1.9, les hachages sont ordonnés par ordre d'insertion des clés.) Donc, il n'aurait pas de sens Avant 1.9 d'itérer sur un hachage de la même manière / séquence que les tableaux, qui ont toujours eu un ordre défini. Je ne sais pas quel est l'ordre par défaut pour les tableaux associatifs PHP (apparemment mon google fu n'est pas assez fort pour comprendre cela non plus) , mais je Je ne sais pas comment vous pouvez considérer les tableaux PHP réguliers et les tableaux associatifs PHP comme "identiques" dans ce contexte, puisque l'ordre des tableaux associatifs semble indéfini.

En tant que tel, la voie Ruby me semble plus claire et intuitive. :)

12
répondu Pistos 2008-11-23 16:19:33

Essayer de faire la même chose de manière cohérente avec les tableaux et les hachages pourrait être juste une odeur de code, mais, au risque d'être étiqueté comme un demi-singe codeur, si vous cherchez un comportement cohérent, cela ferait-il l'affaire?:

class Hash
    def each_pairwise
        self.each { | x, y |
            yield [x, y]
        }
    end
end

class Array
    def each_pairwise
        self.each_with_index { | x, y |
            yield [y, x]
        }
    end
end

["a","b","c"].each_pairwise { |x,y|
    puts "#{x} => #{y}"
}

{"a" => "Aardvark","b" => "Bogle","c" => "Catastrophe"}.each_pairwise { |x,y|
    puts "#{x} => #{y}"
}
7
répondu Brent.Longborough 2008-11-22 13:57:01

J'avais essayé de construire un menu (dans Camping et Markaby) en utilisant un hachage.

Chaque élément a 2 éléments: une étiquette de menu et une URL , donc un hachage semblait correct, mais l'URL ' / 'pour' Home ' apparaissait toujours en dernier (comme vous vous attendez à un hachage), donc les éléments de menu apparaissaient dans le mauvais ordre.

L'utilisation d'un tableau avec each_slice fait le travail:

['Home', '/', 'Page two', 'two', 'Test', 'test'].each_slice(2) do|label,link|
   li {a label, :href => link}
end

Ajouter des valeurs supplémentaires pour chaque élément de menu (par exemple, comme un CSS ID name) signifie simplement augmenter le la tranche de la valeur. Donc, comme un hachage mais avec des groupes composés d'un nombre quelconque d'éléments. Parfait.

Donc, c'est juste pour dire merci d'avoir fait allusion par inadvertance à une solution!

Évident, mais mérite d'être déclaré: Je suggère de vérifier si la longueur du tableau est divisible par la valeur de la tranche.

5
répondu Dave Everitt 2012-05-28 07:27:09

Voici les quatre options énumérées dans votre question, classées par liberté de contrôle. Vous voudrez peut-être en utiliser un autre en fonction de ce dont vous avez besoin.

  1. Passez simplement par les valeurs:

    array.each
    
  2. Passez simplement par les indices:

    array.each_index
    
  3. Passer Par indices + variable d'index:

    for i in array
    
  4. Nombre de boucles de contrôle + variable d'index:

    array.length.times do | i |
    
5
répondu Jake Nelken 2015-08-21 18:08:34

Si vous utilisez le mixin enumerable (comme le fait Rails), vous pouvez faire quelque chose de similaire à l'extrait PHP répertorié. Utilisez simplement la méthode each_slice et aplatissez le hachage.

require 'enumerator' 

['a',1,'b',2].to_a.flatten.each_slice(2) {|x,y| puts "#{x} => #{y}" }

# is equivalent to...

{'a'=>1,'b'=>2}.to_a.flatten.each_slice(2) {|x,y| puts "#{x} => #{y}" }

Moins de correctifs de singe requis.

Cependant, cela pose des problèmes lorsque vous avez un tableau récursif ou un hachage avec des valeurs de tableau. Dans ruby 1.9, ce problème est résolu avec un paramètre de la méthode aplatir qui spécifie la profondeur de récursivité.

# Ruby 1.8
[1,2,[1,2,3]].flatten
=> [1,2,1,2,3]

# Ruby 1.9
[1,2,[1,2,3]].flatten(0)
=> [1,2,[1,2,3]]

Quant à la question de savoir s'il s'agit d'un odeur de code, Je ne suis pas sûr. Habituellement, quand je dois me pencher en arrière pour itérer sur quelque chose, je recule et réalise que j'attaque mal le problème.

3
répondu maxhawkins 2009-01-13 17:38:02

La bonne façon est celle avec laquelle vous vous sentez le plus à l'aise et qui fait ce que vous voulez qu'elle fasse. Dans la programmation, il y a rarement une façon "correcte" de faire les choses, le plus souvent il y a plusieurs façons de choisir.

Si vous êtes à l'aise avec certaines façons de faire, faites - le, à moins que cela ne fonctionne pas-alors il est temps de trouver une meilleure façon.

2
répondu Dariusz G. Jagielski 2014-01-09 16:52:14

Dans Ruby 2.1, la méthode each_with_index est supprimée. Au lieu de cela, vous pouvez utiliser each_index

Exemple:

a = [ "a", "b", "c" ]
a.each_index {|x| print x, " -- " }

Produit:

0 -- 1 -- 2 --
2
répondu Amjed Shareef 2015-05-13 13:45:08