Was ist der „richtige“ Weg, um ein Array in Ruby zu durchlaufen?

341

PHP ist trotz all seiner Warzen in dieser Hinsicht ziemlich gut. Es gibt keinen Unterschied zwischen einem Array und einem Hash (vielleicht bin ich naiv, aber das scheint mir offensichtlich richtig zu sein), und um es zu durchlaufen, tun Sie es einfach

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

In Ruby gibt es eine Reihe von Möglichkeiten, dies zu tun:

array.length.times do |i|
end

array.each

array.each_index

for i in array

Hashes sind sinnvoller, da ich sie einfach immer benutze

hash.each do |key, value|

Warum kann ich das nicht für Arrays tun? Wenn ich mich nur an eine Methode erinnern möchte, kann ich sie vermutlich verwenden each_index(da dadurch sowohl der Index als auch der Wert verfügbar sind), aber es ist ärgerlich, dies tun zu müssen, array[index]anstatt nur value.


Oh, richtig, ich habe es vergessen array.each_with_index. Dieser ist jedoch scheiße, weil er geht |value, key|und hash.eachgeht |key, value|! Ist das nicht verrückt?

Tom Lehman
quelle
1
Ich denke, array#each_with_indexverwendet, |value, key|weil der Methodenname die Reihenfolge impliziert, während die Reihenfolge für hash#eachdie hash[key] = valueSyntax imitiert ?
Benjineer
3
Wenn Sie gerade erst mit Schleifen in Ruby beginnen, dann überprüfen Sie mit auswählen, ablehnen, sammeln, injizieren und erkennen
Matthew Carriere

Antworten:

560

Dies wird alle Elemente durchlaufen:

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

Drucke:

1
2
3
4
5
6

Dadurch werden alle Elemente durchlaufen, die Ihnen den Wert und den Index geben:

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

Drucke:

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

Ich bin mir bei Ihrer Frage nicht ganz sicher, welche Sie suchen.

Robert Gamble
quelle
1
Off-Topic Ich kann nicht each_with_index in Ruby 1.9 im Array finden
CantGetANick
19
@CantGetANick: Die Array-Klasse enthält das Enumerable-Modul mit dieser Methode.
xuinkrbin.
87

Ich denke, es gibt keinen richtigen Weg. Es gibt viele verschiedene Möglichkeiten zum Iterieren, und jede hat ihre eigene Nische.

  • each ist für viele Verwendungen ausreichend, da mir die Indizes nicht oft wichtig sind.
  • each_ with _index verhält sich wie Hash # each - Sie erhalten den Wert und den Index.
  • each_index- nur die Indizes. Ich benutze diesen nicht oft. Entspricht "length.times".
  • map ist eine weitere Möglichkeit zum Iterieren, die nützlich ist, wenn Sie ein Array in ein anderes umwandeln möchten.
  • select ist der Iterator, der verwendet werden soll, wenn Sie eine Teilmenge auswählen möchten.
  • inject ist nützlich, um Summen oder Produkte zu generieren oder ein einzelnes Ergebnis zu sammeln.

Es mag viel erscheinen, sich zu erinnern, aber keine Sorge, Sie können auskommen, ohne alle zu kennen. Aber wenn Sie anfangen, die verschiedenen Methoden zu lernen und anzuwenden, wird Ihr Code sauberer und klarer und Sie sind auf dem Weg zur Ruby-Meisterschaft.

AShelly
quelle
gute Antwort! Ich möchte das Gegenteil wie #reject und Aliase wie #collect erwähnen.
Emily Tui Ch
60

Ich sage nicht, dass Array-> |value,index|und Hash-> |key,value|nicht verrückt sind (siehe Horace Loebs Kommentar), aber ich sage, dass es einen vernünftigen Weg gibt, dieses Arrangement zu erwarten.

Wenn ich mich mit Arrays beschäftige, konzentriere ich mich auf die Elemente im Array (nicht auf den Index, da der Index vorübergehend ist). Die Methode ist jeweils mit Index, dh jedem + Index oder | jedem Index | oder |value,index|. Dies stimmt auch mit dem Index überein, der als optionales Argument angesehen wird, z. B. | value | entspricht | value, index = nil | Dies stimmt mit | value, index | überein.

Wenn ich mich mit Hashes beschäftige, konzentriere ich mich oft mehr auf die Schlüssel als auf die Werte, und ich beschäftige mich normalerweise entweder mit Schlüsseln und Werten in dieser Reihenfolge, entweder key => valueoder hash[key] = value.

Wenn Sie Enten tippen möchten, verwenden Sie entweder explizit eine definierte Methode, wie Brent Longborough gezeigt hat, oder eine implizite Methode, wie Maxhawkins gezeigt hat.

Bei Ruby geht es darum, die Sprache dem Programmierer anzupassen, nicht darum, dass der Programmierer der Sprache entspricht. Deshalb gibt es so viele Möglichkeiten. Es gibt so viele Möglichkeiten, über etwas nachzudenken. In Ruby wählen Sie den nächstgelegenen und der Rest des Codes fällt normalerweise äußerst ordentlich und präzise aus.

Was die ursprüngliche Frage betrifft: "Was ist der" richtige "Weg, um ein Array in Ruby zu durchlaufen?", Nun, ich denke, der Kernweg (dh ohne starken syntaktischen Zucker oder objektorientierte Kraft) besteht darin, Folgendes zu tun:

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

Aber Ruby dreht sich alles um mächtigen syntaktischen Zucker und objektorientierte Kraft, aber hier ist das Äquivalent für Hashes, und die Schlüssel können bestellt werden oder nicht:

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

Meine Antwort lautet also: "Der" richtige "Weg, ein Array in Ruby zu durchlaufen, hängt von Ihnen (dh dem Programmierer oder dem Programmierteam) und dem Projekt ab." Der bessere Ruby-Programmierer trifft die bessere Wahl (welche syntaktische Kraft und / oder welcher objektorientierte Ansatz). Der bessere Ruby-Programmierer sucht weiterhin nach mehr Möglichkeiten.


Jetzt möchte ich eine weitere Frage stellen: "Was ist der" richtige "Weg, um einen Bereich in Ruby rückwärts zu durchlaufen?"! (Diese Frage ist, wie ich zu dieser Seite gekommen bin.)

Es ist schön zu tun (für die Stürmer):

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

aber ich mache nicht gerne (für die rückwärts):

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

Nun, es macht mir eigentlich nichts aus, das zu viel zu tun, aber wenn ich rückwärts unterrichte, möchte ich meinen Schülern eine schöne Symmetrie zeigen (dh mit minimalem Unterschied, z. B. nur eine Umkehrung oder einen Schritt -1 hinzufügen, aber ohne etwas anderes ändern). Sie können Folgendes tun (aus Symmetriegründen):

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

und

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

was ich nicht sehr mag, aber du kannst es nicht tun

(*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

Sie könnten letztendlich tun

class Range

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

end

Aber ich möchte (noch) eher reinen Ruby als objektorientierte Ansätze lehren. Ich möchte rückwärts iterieren:

  • ohne ein Array zu erstellen (0..1000000000 berücksichtigen)
  • Arbeiten für jeden Bereich (z. B. Zeichenfolgen, nicht nur Ganzzahlen)
  • ohne zusätzliche objektorientierte Kraft (dh keine Klassenänderung)

Ich glaube, dass dies unmöglich ist, ohne eine predMethode zu definieren , was bedeutet, dass die Range-Klasse geändert wird, um sie zu verwenden. Wenn Sie dies tun können, lassen Sie es mich bitte wissen, andernfalls wäre eine Bestätigung der Unmöglichkeit willkommen, obwohl dies enttäuschend wäre. Vielleicht spricht Ruby 1.9 dies an.

(Vielen Dank für Ihre Zeit beim Lesen.)


quelle
5
1.upto(10) do |i| puts i endund 10.downto(1) do puts i endgibt irgendwie die Symmetrie, die Sie wollten. Ich hoffe, das hilft. Ich bin mir nicht sicher, ob es für Strings und dergleichen funktionieren würde.
Suren
(1..10) .to_a.sort {| x, y | y <=> x} .each {| i | setzt "i = # {i}"} - umgekehrt ist langsamer.
Davidslv
Du kannst [*1..10].each{|i| puts "i=#{i}" }.
Hauleth
19

Verwenden Sie each_with_index, wenn Sie beide benötigen.

ary.each_with_index { |val, idx| # ...
J Cooper
quelle
12

Die anderen Antworten sind in Ordnung, aber ich wollte auf eine andere periphere Sache hinweisen: Arrays sind geordnet, während Hashes nicht in 1.8 enthalten sind. (In Ruby 1.9 werden Hashes nach Einfügereihenfolge der Schlüssel sortiert.) Daher wäre es vor 1.9 nicht sinnvoll, einen Hash auf dieselbe Weise / in derselben Reihenfolge wie Arrays zu durchlaufen, die immer eine bestimmte Reihenfolge hatten. Ich weiß nicht, wie die Standardreihenfolge für assoziative PHP-Arrays lautet (anscheinend ist mein Google Fu auch nicht stark genug, um das herauszufinden), aber ich weiß nicht, wie Sie reguläre PHP-Arrays und assoziative PHP-Arrays berücksichtigen können in diesem Zusammenhang "gleich" sein, da die Reihenfolge für assoziative Arrays undefiniert zu sein scheint.

Als solches erscheint mir der Ruby-Weg klarer und intuitiver. :) :)

Pistos
quelle
1
Hashes und Arrays sind dasselbe! Arrays ordnen Ganzzahlen Objekten zu und Hashes ordnen Objekte Objekten zu. Arrays sind nur Sonderfälle von Hashes, oder?
Tom Lehman
1
Wie gesagt, ein Array ist eine geordnete Menge. Ein Mapping (im allgemeinen Sinne) ist ungeordnet. Wenn Sie den Schlüsselsatz auf Ganzzahlen beschränken (z. B. bei Arrays), hat der Schlüsselsatz nur eine Reihenfolge. In einer generischen Zuordnung (Hash / Associative Array) haben die Schlüssel möglicherweise keine Reihenfolge.
Pistos
@ Horace: Hashes und Arrays sind nicht dasselbe. Wenn einer ein besonderer Fall des anderen ist, können sie nicht gleich sein. Schlimmer noch, Arrays sind keine spezielle Art von Hashes, sondern nur eine abstrakte Sichtweise. Wie Brent oben ausgeführt hat, kann die austauschbare Verwendung von Hashes und Arrays auf einen Codegeruch hinweisen.
Zane
9

Der Versuch, dasselbe konsequent mit Arrays und Hashes zu tun, könnte nur ein Codegeruch sein, aber auf die Gefahr hin, als codoröser Halbaffen-Patcher gebrandmarkt zu werden, würde dies den Trick tun, wenn Sie nach konsistentem Verhalten suchen ?:

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}"
}
Brent.Longborough
quelle
7

Hier sind die vier in Ihrer Frage aufgeführten Optionen, geordnet nach Kontrollfreiheit. Je nachdem, was Sie benötigen, möchten Sie möglicherweise eine andere verwenden.

  1. Gehen Sie einfach die Werte durch:

    array.each
  2. Gehen Sie einfach die Indizes durch:

    array.each_index
  3. Gehen Sie durch Indizes + Indexvariable:

    for i in array
  4. Regelkreisanzahl + Indexvariable:

    array.length.times do | i |
Jake
quelle
5

Ich hatte versucht, ein Menü (in Camping und Markaby ) mit einem Hash zu erstellen .

Jedes Element hat zwei Elemente: eine Menübezeichnung und eine URL , sodass ein Hash richtig schien, aber die '/' URL für 'Home' wurde immer zuletzt angezeigt (wie Sie es von einem Hash erwarten würden), sodass Menüelemente falsch angezeigt wurden Auftrag.

Die Verwendung eines Arrays mit each_sliceerledigt die Aufgabe:

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

Das Hinzufügen zusätzlicher Werte für jeden Menüpunkt (z. B. wie ein CSS- ID- Name) bedeutet lediglich das Erhöhen des Slice-Werts. Also wie ein Hash, aber mit Gruppen, die aus einer beliebigen Anzahl von Elementen bestehen. Perfekt.

Dies ist nur ein Dankeschön für den versehentlichen Hinweis auf eine Lösung!

Offensichtlich, aber erwähnenswert: Ich schlage vor, zu überprüfen, ob die Länge des Arrays durch den Slice-Wert teilbar ist.

Dave Everitt
quelle
4

Wenn Sie das aufzählbare Mixin verwenden (wie es Rails tut), können Sie etwas Ähnliches wie das aufgelistete PHP-Snippet tun. Verwenden Sie einfach die each_slice-Methode und reduzieren Sie den Hash.

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}" }

Weniger Affen-Patching erforderlich.

Dies führt jedoch zu Problemen, wenn Sie ein rekursives Array oder einen Hash mit Array-Werten haben. In Ruby 1.9 wird dieses Problem mit einem Parameter für die Abflachungsmethode gelöst, der angibt, wie tief die Wiederholung erfolgen soll.

# 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]]

Was die Frage betrifft, ob dies ein Codegeruch ist, bin ich mir nicht sicher. Wenn ich mich nach hinten beugen muss, um über etwas zu iterieren, trete ich normalerweise zurück und stelle fest, dass ich das Problem falsch angreife.

Maxhawkins
quelle
2

Der richtige Weg ist der, mit dem Sie sich am wohlsten fühlen und der das tut, was Sie wollen. In der Programmierung gibt es selten eine "richtige" Art, Dinge zu tun, häufiger gibt es mehrere Möglichkeiten zur Auswahl.

Wenn Sie mit bestimmten Methoden vertraut sind, tun Sie es einfach, es sei denn, es funktioniert nicht - dann ist es Zeit, einen besseren Weg zu finden.

Dariusz G. Jagielski
quelle
2

In Ruby 2.1 wird die Methode each_with_index entfernt. Stattdessen können Sie each_index verwenden

Beispiel:

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

produziert:

0 -- 1 -- 2 --
Amjed Shareef
quelle
4
Das ist nicht wahr. Wie in den Kommentaren der akzeptierten Antwort angegeben, erscheint der Grund each_with_indexnicht in der Dokumentation, weil er vom EnumerableModul bereitgestellt wird .
Ihaztehcodez
0

Die Verwendung derselben Methode zum Durchlaufen von Arrays und Hashes ist sinnvoll, um beispielsweise verschachtelte Hash- und Array-Strukturen zu verarbeiten, die häufig aus Parsern, dem Lesen von JSON-Dateien usw. resultieren.

Eine clevere Methode, die noch nicht erwähnt wurde, ist die Vorgehensweise in der Ruby Facets- Bibliothek mit Standardbibliothekserweiterungen. Von hier aus :

class Array

  # Iterate over index and value. The intention of this
  # method is to provide polymorphism with Hash.
  #
  def each_pair #:yield:
    each_with_index {|e, i| yield(i,e) }
  end

end

Es gibt bereits Hash#each_paireinen Alias ​​von Hash#each. Nach diesem Patch haben Array#each_pairund können wir ihn auch austauschbar verwenden, um sowohl Hashes als auch Arrays zu durchlaufen. Dies behebt den beobachteten Wahnsinn des OP, bei dem Array#each_with_indexdie Blockargumente im Vergleich zu umgekehrt sind Hash#each. Anwendungsbeispiel:

my_array = ['Hello', 'World', '!']
my_array.each_pair { |key, value| pp "#{key}, #{value}" }

# result: 
"0, Hello"
"1, World"
"2, !"

my_hash = { '0' => 'Hello', '1' => 'World', '2' => '!' }
my_hash.each_pair { |key, value| pp "#{key}, #{value}" }

# result: 
"0, Hello"
"1, World"
"2, !"
Tanius
quelle