Care este modul „corect” de a itera printr-un array în Ruby? (Programare, Ruby, Array-Uri, Bucle)

Tom Lehman a intrebat.

PHP, cu toate neajunsurile sale, este destul de bun în acest sens. Nu există nicio diferență între o matrice și un hash (poate că sunt naiv, dar mie mi se pare evident că este corect), iar pentru a itera prin oricare dintre ele trebuie doar să faci următoarele operații

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

În Ruby există o grămadă de moduri de a face acest tip de lucru:

array.length.times do |i|
end

array.each

array.each_index

for i in array

Hashurile au mai mult sens, din moment ce eu folosesc întotdeauna

hash.each do |key, value|

De ce nu pot face acest lucru pentru array-uri? Dacă vreau să-mi amintesc doar o metodă, cred că pot folosi each_index (deoarece pune la dispoziție atât indexul, cât și valoarea), dar este enervant să trebuiască să fac array[index] în loc de doar value.


Ah, da, am uitat de array.each_with_index. Cu toate acestea, acesta este nașpa pentru că merge |value, key| și hash.each merge |key, value|! Nu este o nebunie?

Comentarii

  • Eu cred că array#each_with_index folosește |value, key| pentru că numele metodei implică ordinea, în timp ce ordinea folosită pentru hash#each imită hash[key] = value sintaxa? –  > Por Benjineer.
  • Dacă sunteți la început cu buclele în Ruby, atunci consultați folosind select, reject, collect, inject și detect –  > Por Matthew Carriere.
12 răspunsuri
Robert Gamble

Acest lucru va itera prin toate elementele:

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

Tipărituri:

1
2
3
4
5
6

Aceasta va parcurge toate elementele, oferindu-vă valoarea și indexul:

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

Imprimă:

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

Nu sunt foarte sigur, după întrebarea dumneavoastră, care dintre ele este cea pe care o căutați.

Comentarii

  • Off-topic Nu găsesc each_with_index în ruby 1.9 în array –  > Por CantGetANick.
  • 19

  • @CantGetANick: Clasa Array include modulul Enumerable care are această metodă. –  > Por xuinkrbin..
AShelly

Cred că nu există o corect mod. Există o mulțime de moduri diferite de a itera și fiecare are propria nișă.

  • each este suficient pentru multe utilizări, deoarece nu-mi pasă adesea de indici.
  • each_ with _index acționează ca Hash#each – se obține valoarea și indexul.
  • each_index – doar indicii. Pe acesta nu îl folosesc des. Echivalent cu „length.times”.
  • map este o altă modalitate de a itera, utilă atunci când doriți să transformați o matrice în alta.
  • select este iteratorul care trebuie folosit atunci când doriți să alegeți un subset.
  • inject este util pentru a genera sume sau produse sau pentru a colecta un singur rezultat.

Poate părea mult de reținut, dar nu vă faceți griji, vă puteți descurca și fără să le cunoașteți pe toate. Dar, pe măsură ce începeți să învățați și să utilizați diferitele metode, codul dumneavoastră va deveni mai curat și mai clar, iar dumneavoastră veți fi pe drumul spre stăpânirea Ruby.

Comentarii

  • Un răspuns excelent! Aș dori să menționez inversul, cum ar fi #reject și alias-uri ca #collect. –  > Por Emily Tui Ch.
Luis Esteban

Nu spun că Array -> |value,index| și Hash -> |key,value| nu este o nebunie (a se vedea comentariul lui Horace Loeb), dar spun că există o modalitate sănătoasă de a aștepta acest aranjament.

Când am de-a face cu array-uri, mă concentrez asupra elementelor din array (nu asupra indicelui, deoarece indicele este tranzitoriu). Metoda este fiecare cu indicele, adică fiecare+index, sau |fiecare,indice|, sau |value,index|. Acest lucru este, de asemenea, în concordanță cu faptul că indicele este considerat un argument opțional, de exemplu, |value| este echivalent cu |value,index=nil|, ceea ce este în concordanță cu |value,index|.

Când am de-a face cu hașuri, mă concentrez adesea mai mult pe chei decât pe valori și, de obicei, mă ocup de chei și valori în această ordine, fie key => value fie hash[key] = value.

Dacă doriți să folosiți duck-typing, atunci fie folosiți în mod explicit o metodă definită, așa cum a arătat Brent Longborough, fie o metodă implicită, așa cum a arătat maxhawkins.

În Ruby este vorba despre adaptarea limbajului pentru a se potrivi programatorului, nu despre adaptarea programatorului pentru a se potrivi limbajului. Acesta este motivul pentru care există atât de multe moduri. Există atât de multe moduri de a gândi despre ceva. În Ruby, îl alegi pe cel mai apropiat, iar restul codului cade, de obicei, extrem de curat și concis.

În ceea ce privește întrebarea inițială, „Care este modalitatea „corectă” de a itera printr-o matrice în Ruby?”, ei bine, cred că modalitatea de bază (adică fără zahăr sintactic puternic sau putere orientată pe obiecte) este de a face:

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

Dar în Ruby este vorba despre zahăr sintactic puternic și putere orientată pe obiecte, dar, oricum, iată echivalentul pentru hașuri, iar cheile pot fi ordonate sau nu:

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

Așadar, răspunsul meu este: „Modul „corect” de a itera printr-o matrice în Ruby depinde de dumneavoastră (adică de programator sau de echipa de programare) și de proiect.”. Cel mai bun programator Ruby face cea mai bună alegere (a cărei puteri sintactice și/sau cărei abordări orientate pe obiecte). Cel mai bun programator Ruby continuă să caute mai multe modalități.


Acum, vreau să pun o altă întrebare: „Care este modul „corect” de a itera printr-un Range în Ruby invers?”! (Această întrebare este modul în care am ajuns la această pagină).

Este frumos de făcut (pentru forward):

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

dar nu-mi place să fac (pentru invers):

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

Ei bine, de fapt, nu mă deranjează prea mult să fac asta, dar când predau mersul înapoi, vreau să le arăt studenților mei o simetrie frumoasă (adică cu o diferență minimă, de exemplu, adăugând doar un invers, sau un pas -1, dar fără a modifica nimic altceva). puteți face (pentru simetrie):

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

și

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

ceea ce nu-mi place prea mult, dar nu poți face

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

În ultimă instanță, puteți face

class Range

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

end

dar vreau să predau Ruby pur, mai degrabă decât abordări orientate pe obiecte (încă). Aș dori să iternez în sens invers:

  • fără a crea o matrice (luați în considerare 0..1000000000)
  • să funcționeze pentru orice interval (de exemplu, șiruri de caractere, nu doar numere întregi)
  • fără a utiliza vreo putere suplimentară orientată pe obiecte (adică fără modificarea claselor)

Cred că acest lucru este imposibil fără a defini un pred ceea ce înseamnă modificarea clasei Range pentru a o utiliza. Dacă puteți face acest lucru, vă rog să mă anunțați, în caz contrar, confirmarea imposibilității ar fi apreciată, deși ar fi dezamăgitoare. Poate că Ruby 1.9 rezolvă această problemă.

(Mulțumesc pentru timpul acordat citirii acestui articol).

Comentarii

  • 1.upto(10) do |i| puts i end și 10.downto(1) do puts i end oferă într-un fel simetria pe care o doreați. Sper că vă ajută. Nu sunt sigur dacă ar funcționa pentru șiruri de caractere și altele asemenea. –  > Por Suren.
  • (1..10).to_a.sort{ |x,y| y <=> x }.each{|i| puts „i=#{i}” } – invers este mai lent. –  > Por Davidslv.
  • Puteți [*1..10].each{|i| puts "i=#{i}" }. –  > Por Hauleth.
J. Cooper

Utilizați each_with_index atunci când aveți nevoie de ambele.

ary.each_with_index { |val, idx| # ...

Pistos

Celelalte răspunsuri sunt foarte bune, dar am vrut să subliniez un alt lucru periferic: Array-urile sunt ordonate, în timp ce Hash-urile nu sunt în 1.8. (În Ruby 1.9, Hashurile sunt ordonate în funcție de ordinea de inserție a cheilor.) Deci, înainte de 1.9, nu ar avea sens să iterați peste un Hash în același mod/secvență ca și Arrayurile, care au avut întotdeauna o ordine definită. Nu știu care este ordinea implicită pentru array-urile asociative din PHP (se pare că nici google fu-ul meu nu este suficient de puternic pentru a afla asta), dar nu știu cum puteți considera array-urile obișnuite din PHP și array-urile asociative din PHP ca fiind „la fel” în acest context, din moment ce ordinea pentru array-urile asociative pare nedefinită.

Ca atare, metoda Ruby mi se pare mai clară și mai intuitivă. 🙂

Comentarii

  • hashes și array-urile sunt același lucru! array-urile mapează numere întregi în obiecte, iar hashes-urile mapează obiecte în obiecte. array-urile sunt doar cazuri speciale de hashes, nu? –  > Por Tom Lehman.
  • După cum am spus, un array este un set ordonat. O cartografiere (în sens generic) este neordonată. Dacă restricționați setul de chei la numere întregi (ca în cazul tablourilor), se întâmplă ca setul de chei să aibă o ordine. Într-o cartografiere generică (Hash / Array asociativ), este posibil ca cheile să nu aibă o ordine. –  > Por Pistos.
  • @Horace: Hashurile și array-urile nu sunt același lucru. Dacă unul este un caz special al celuilalt, nu pot fi la fel. Dar și mai rău, array-urile nu sunt un fel special de hashes, acesta este doar un mod abstract de a le vedea. După cum a subliniat Brent mai sus, folosirea hașurilor și a matricelor în mod interschimbabil ar putea indica un miros de cod. –  > Por Zane.
Jake

Iată cele patru opțiuni enumerate în întrebarea dvs., aranjate în funcție de libertatea de control. S-ar putea să doriți să folosiți una diferită în funcție de ceea ce aveți nevoie.

  1. Pur și simplu treceți prin valori:

    array.each
    
  2. Treceți pur și simplu prin indici:

    array.each_index
    
  3. Treceți prin indici + variabila index:

    for i in array
    
  4. Controlați numărul de bucle + variabila index:

    array.length.times do | i |
    

Brent.Longborough

Încercarea de a face același lucru în mod consecvent cu array-uri și hașuri poate să fie doar un miros de cod, dar, cu riscul de a fi catalogat ca fiind un semimonitor de coduri, dacă sunteți în căutarea unui comportament coerent, ar fi aceasta o soluție?:

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

Dave Everitt

Am încercat să construiesc un meniu (în format Camping și Markaby) folosind un hash.

Fiecare element are 2 elemente: a eticheta meniului și un element URL, astfel încât un hash părea corect, dar URL-ul „/” pentru „Home” apărea întotdeauna ultimul (așa cum v-ați aștepta pentru un hash), astfel încât elementele de meniu apăreau în ordine greșită.

Utilizarea unei matrice cu each_slice face treaba:

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

Adăugarea de valori suplimentare pentru fiecare element de meniu (de exemplu, ca un CSS ID name) înseamnă doar creșterea valorii slice-ului. Așadar, ca un hash, dar cu grupuri formate din orice număr de elemente. Perfect.

Deci, asta doar pentru a vă mulțumi pentru că ați făcut aluzie, fără să vreți, la o soluție!

Evident, dar merită spus: Vă sugerez să verificați dacă lungimea matricei este divizibilă cu valoarea feliei.

maxhawkins

Dacă folosiți enumerable mixin (așa cum face Rails) puteți face ceva similar cu fragmentul php listat. Folosiți doar metoda each_slice și aplatizați hash-ul.

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

Mai puțin monkey-patching necesar.

Cu toate acestea, acest lucru cauzează probleme atunci când aveți o matrice recursivă sau un hash cu valori de matrice. În ruby 1.9, această problemă este rezolvată cu un parametru pentru metoda flatten care specifică cât de adânc trebuie să se recurgă.

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

În ceea ce privește întrebarea dacă acesta este un miros de cod, nu sunt sigur. De obicei, atunci când trebuie să mă aplec asupra a ceva pentru a itera ceva, fac un pas înapoi și îmi dau seama că atac problema greșit.

Amjed Shareef

În Ruby 2.1, metoda each_with_index este eliminată. în schimb, puteți folosi each_index

Exemplu:

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

produce:

0 -- 1 -- 2 --

Comentarii

  • Acest lucru nu este adevărat. După cum se precizează în comentariile răspunsului acceptat, motivul each_with_index nu apare în documentație este pentru că este furnizat de către Enumerable modul. –  > Por ihaztehcodez.
Dariusz G. Jagielski

Calea corectă este cea cu care vă simțiți cel mai confortabil și care face ceea ce doriți să facă. În programare, rareori există un singur mod „corect” de a face lucrurile, mai des există mai multe moduri de a alege.

Dacă vă simțiți confortabil cu un anumit mod de a face lucrurile, faceți-o, doar dacă nu funcționează – atunci este timpul să găsiți o cale mai bună.

tanius

Folosirea aceleiași metode pentru iterația prin matrici și hash-uri are sens, de exemplu, pentru a procesa structuri hash și matrice imbricate, care rezultă adesea din parser, din citirea fișierelor JSON etc..

O modalitate inteligentă care nu a fost încă menționată este modul în care se face în Ruby Facets a extensiilor bibliotecii standard. De la aici:

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

Există deja Hash#each_pair, un alias al Hash#each. Deci, după acest patch, avem de asemenea Array#each_pair și îl putem folosi în mod interschimbabil pentru a itera atât prin Hash-uri, cât și prin Array-uri. Acest lucru remediază nebunia observată de OP, conform căreia Array#each_with_index are argumentele blocului inversate în comparație cu Hash#each. Exemplu de utilizare:

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, !"