Jaki jest „właściwy” sposób na iterację w tablicy w Ruby?

341

PHP, pomimo wszystkich brodawek, jest całkiem niezłe pod tym względem. Nie ma różnicy między tablicą a skrótem (być może jestem naiwny, ale wydaje mi się to oczywiście w porządku) i iteracja po obu stronach

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

W Ruby istnieje wiele sposobów na zrobienie czegoś takiego:

array.length.times do |i|
end

array.each

array.each_index

for i in array

Hashe mają większy sens, ponieważ po prostu zawsze używam

hash.each do |key, value|

Dlaczego nie mogę tego zrobić dla tablic? Jeśli chcę zapamiętać tylko jedną metodę, myślę, że mogę jej użyć each_index(ponieważ udostępnia ona zarówno indeks, jak i wartość), ale denerwujące jest to, że trzeba to robić array[index]zamiast tylko value.


No tak, zapomniałem o tym array.each_with_index. Jednak ten jest do bani, ponieważ idzie |value, key|i hash.eachidzie |key, value|! Czy to nie jest szalone?

Tom Lehman
źródło
1
Sądzę, że array#each_with_indexużywa, |value, key|ponieważ nazwa metody implikuje kolejność, podczas gdy kolejność stosowana do hash#eachnaśladowania hash[key] = valueskładni?
Benjineer,
3
Jeśli dopiero zaczynasz korzystać z pętli w Ruby, to sprawdź, używając select, odrzucaj, zbieraj, wstrzykuj i wykrywaj
Matthew Carriere

Odpowiedzi:

560

Spowoduje to iterację wszystkich elementów:

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

Wydruki:

1
2
3
4
5
6

Spowoduje to iterację wszystkich elementów podających wartość i indeks:

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

Wydruki:

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

Nie jestem do końca pewien twojego pytania, którego szukasz.

Robert Gamble
źródło
1
Nie na temat Nie mogę znaleźć each_with_index w ruby ​​1.9 w tablicy
CantGetANick
19
@CantGetANick: klasa Array zawiera moduł Enumerable, który ma tę metodę.
xuinkrbin.
88

Myślę, że nie ma jednej właściwej drogi. Istnieje wiele różnych sposobów iteracji, a każda z nich ma swoją niszę.

  • each jest wystarczający dla wielu zastosowań, ponieważ często nie dbam o indeksy.
  • each_ with _index działa jak Hash # każdy - dostajesz wartość i indeks.
  • each_index- tylko indeksy. Nie używam tego często. Odpowiednik „length.times”.
  • map to kolejny sposób iteracji, przydatny, gdy chcesz przekształcić jedną tablicę w drugą.
  • select jest iteratorem używanym, gdy chcesz wybrać podzbiór.
  • inject jest przydatny do generowania sum lub produktów lub zbierania pojedynczych wyników.

To może wydawać się dużo do zapamiętania, ale nie martw się, możesz sobie poradzić nie znając ich wszystkich. Ale kiedy zaczniesz się uczyć i korzystać z różnych metod, twój kod stanie się bardziej przejrzysty i będziesz na najlepszej drodze do opanowania języka Ruby.

AShelly
źródło
świetna odpowiedź! Chciałbym wspomnieć o odwrotności jak #reject i aliasach jak #collect.
Emily Tui Ch
60

Nie twierdzę, że Array-> |value,index|i Hash-> |key,value|nie jest szalone (patrz komentarz Horace'a Loeba), ale mówię, że istnieje rozsądny sposób, aby spodziewać się takiego rozwiązania.

Kiedy mam do czynienia z tablicami, koncentruję się na elementach tablicy (nie na indeksie, ponieważ indeks jest przejściowy). Każda metoda ma indeks, tzn. Każdy + indeks lub | każdy, indeks | lub |value,index|. Jest to również spójne z indeksem postrzeganym jako opcjonalny argument, np. | Wartość | jest równoważne | wartości, indeks = zero | co jest zgodne z | wartością, indeksem |.

Kiedy mam do czynienia z mieszań, jestem często bardziej koncentruje się na klawiszach od wartości, a ja zwykle do czynienia z kluczy i wartości w tej kolejności, albo key => valuealbo hash[key] = value.

Jeśli chcesz pisać na kaczce, albo jawnie użyj zdefiniowanej metody, jak pokazał Brent Longborough, lub metody niejawnej, jak pokazał maxhawkins.

Ruby polega na dostosowywaniu języka do potrzeb programisty, a nie na dostosowywaniu programisty do języka. Dlatego jest tak wiele sposobów. Jest wiele sposobów myślenia o czymś. W Ruby wybierasz najbliższy, a reszta kodu zwykle wypada wyjątkowo starannie i zwięźle.

Jeśli chodzi o pierwotne pytanie: „Jaki jest„ właściwy ”sposób na iterację w tablicy w Rubim?”, Myślę, że głównym sposobem (tj. Bez potężnego cukru syntaktycznego lub mocy obiektowej) jest:

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

Ale w Ruby chodzi o potężny cukier syntaktyczny i moc obiektową, ale w każdym razie jest to odpowiednik skrótów, a klucze można zamówić lub nie:

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

Zatem moja odpowiedź brzmi: „„ Właściwy ”sposób iteracji w tablicy w Ruby zależy od ciebie (tj. Programisty lub zespołu programistów) i projektu.” Im lepszy programista Ruby, tym lepszy wybór (która moc syntaktyczna i / lub które podejście obiektowe). Lepszy programista Ruby nadal szuka innych sposobów.


Teraz chcę zadać kolejne pytanie: „Jaki jest„ właściwy ”sposób na iterację wsteczną przez zakres w Ruby?”! (To pytanie, jak doszedłem do tej strony).

Miło jest zrobić (dla napastników):

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

ale nie lubię robić (dla wstecz):

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

Cóż, właściwie nie mam nic przeciwko robieniu tego zbyt wiele, ale kiedy uczę cofania się, chcę pokazać moim uczniom niezłą symetrię (tj. Z minimalną różnicą, np. Dodając tylko odwrotność lub krok -1, ale bez modyfikowanie czegokolwiek innego). Możesz zrobić (dla symetrii):

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

i

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

co mi się nie podoba, ale ty nie możesz

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

Możesz ostatecznie zrobić

class Range

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

end

ale chcę uczyć czystego Rubiego zamiast podejść obiektowych (jeszcze). Chciałbym powtórzyć wstecz:

  • bez tworzenia tablicy (rozważ 0..1000000000)
  • praca dla dowolnego zakresu (np. ciągów, nie tylko liczb całkowitych)
  • bez użycia dodatkowej mocy obiektowej (tj. bez modyfikacji klasy)

Uważam, że jest to niemożliwe bez zdefiniowania predmetody, co oznacza modyfikację klasy Range, aby z niej korzystać. Jeśli możesz to zrobić, proszę dać mi znać, w przeciwnym razie potwierdzenie niemożliwości byłoby mile widziane, ale byłoby rozczarowujące. Być może Ruby 1.9 rozwiązuje ten problem.

(Dziękujemy za poświęcony czas na przeczytanie tego.)


źródło
5
1.upto(10) do |i| puts i endi w 10.downto(1) do puts i endpewnym sensie daje pożądaną symetrię. Mam nadzieję, że to pomaga. Nie jestem pewien, czy to zadziała dla łańcuchów i tym podobnych.
Suren
(1..10) .to_a.sort {| x, y | y <=> x} .each {| i | wstawia „i = # {i}”} - cofanie jest wolniejsze.
Davidslv,
Można [*1..10].each{|i| puts "i=#{i}" }.
Hauleth,
19

Użyj each_with_index, gdy potrzebujesz obu.

ary.each_with_index { |val, idx| # ...
J Cooper
źródło
12

Pozostałe odpowiedzi są w porządku, ale chciałem zwrócić uwagę na jeszcze jedną rzecz peryferyjną: tablice są uporządkowane, a skrótów nie ma w wersji 1.8. (W Ruby 1.9 hashe są uporządkowane według kolejności wstawiania kluczy.) Dlatego przed wersją 1.9 nie ma sensu iterowanie skrótu w taki sam sposób / sekwencję jak tablice, które zawsze miały określoną kolejność. Nie wiem, jaka jest domyślna kolejność dla tablic asocjacyjnych PHP (najwyraźniej moje google fu też nie jest wystarczająco silne, aby to rozgryźć), ale nie wiem, jak można rozważyć zwykłe tablice PHP i tablice asocjacyjne PHP, aby być „tym samym” w tym kontekście, ponieważ kolejność tablic asocjacyjnych wydaje się nieokreślona.

Jako taki, sposób Ruby wydaje mi się bardziej przejrzysty i intuicyjny. :)

Pistacje
źródło
1
skróty i tablice to to samo! tablice odwzorowują liczby całkowite na obiekty, a skróty odwzorowują obiekty na obiekty. tablice to tylko specjalne przypadki skrótów, nie?
Tom Lehman
1
Jak powiedziałem, tablica jest uporządkowanym zestawem. Mapowanie (w sensie ogólnym) jest nieuporządkowane. Jeśli ograniczysz zestaw kluczy do liczb całkowitych (np. Z tablicami), tak się składa, że ​​zestaw kluczy ma kolejność. W ogólnym mapowaniu (tablica skrótu / tablica asocjacyjna) klucze mogą nie mieć kolejności.
Pistos
@Horace: Hashe i tablice nie są takie same. Jeśli jeden jest specjalnym przypadkiem drugiego, nie może być taki sam. Co gorsza, tablice nie są szczególnym rodzajem skrótów, to tylko abstrakcyjny sposób ich przeglądania. Jak wskazał powyżej Brent, używanie skrótów i tablic zamiennie może wskazywać na zapach kodu.
Zane
9

Próba zrobienia tego samego konsekwentnie z tablicami i skrótami może być po prostu zapachem kodu, ale ryzykując, że zostanę uznany za korsarza pół-małpiego patchera, jeśli szukasz spójnego zachowania, to załatwi sprawę ?:

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
źródło
7

Oto cztery opcje wymienione w twoim pytaniu, ułożone według wolności kontroli. Możesz użyć innego, w zależności od tego, czego potrzebujesz.

  1. Po prostu przejdź przez wartości:

    array.each
  2. Wystarczy przejrzeć indeksy:

    array.each_index
  3. Przejdź przez indeksy + zmienną indeksu:

    for i in array
  4. Liczba pętli kontrolnych + zmienna indeksu:

    array.length.times do | i |
Jake
źródło
5

Próbowałem zbudować menu (w Camping i Markaby ) przy użyciu skrótu.

Każdy element ma 2 elementy: etykietę menu i adres URL , więc hash wydawał się odpowiedni, ale „/” URL „Home” zawsze pojawiał się jako ostatni (jak można się spodziewać po haszu), więc elementy menu pojawiały się w niewłaściwym miejscu zamówienie.

Użycie tablicy z each_slicewykonuje zadanie:

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

Dodanie dodatkowych wartości do każdego elementu menu (np. Jak nazwa identyfikatora CSS ) oznacza po prostu zwiększenie wartości plasterka. Tak jak skrót, ale z grupami składającymi się z dowolnej liczby elementów. Doskonały.

To dlatego dziękuję za nieumyślne wskazanie rozwiązania!

Oczywiste, ale warte stwierdzenia: sugeruję sprawdzenie, czy długość tablicy jest podzielna przez wartość wycinka.

Dave Everitt
źródło
4

Jeśli używasz wymiennego mixina (podobnie jak Rails), możesz zrobić coś podobnego do wymienionego fragmentu php. Wystarczy użyć metody each_slice i spłaszczyć skrót.

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

Wymagane jest mniej łatania małp.

Powoduje to jednak problemy, gdy masz tablicę rekurencyjną lub skrót z wartościami tablic. W Ruby 1.9 problem ten został rozwiązany za pomocą parametru metody spłaszczania, który określa, jak głęboko ma się powtarzać.

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

Jeśli chodzi o pytanie, czy to zapach kodu, nie jestem pewien. Zwykle, gdy muszę pochylić się do tyłu, aby powtórzyć coś, cofam się i zdaję sobie sprawę, że źle atakuję problem.

maxhawkins
źródło
2

Właściwy sposób to taki, w którym czujesz się najlepiej i który robi to, co chcesz. W programowaniu rzadko jest jeden „poprawny” sposób robienia rzeczy, częściej istnieje wiele sposobów wyboru.

Jeśli czujesz się dobrze z pewnym sposobem robienia rzeczy, rób to, chyba że to nie działa - to czas znaleźć lepszy sposób.

Dariusz G. Jagielski
źródło
2

W Ruby 2.1 metoda each_with_index jest usuwana. Zamiast tego możesz użyć each_index

Przykład:

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

produkuje:

0 -- 1 -- 2 --
Amjed Shareef
źródło
4
To nie jest prawda. Jak stwierdzono w komentarzach do zaakceptowanej odpowiedzi, przyczyna each_with_indexnie pojawia się w dokumentacji, ponieważ została podana przez Enumerablemoduł.
ihaztehcodez
0

Zastosowanie tej samej metody do iteracji zarówno tablic, jak i skrótów ma sens, na przykład do przetwarzania zagnieżdżonych struktur skrótów i tablic często wynikających z analizatorów składni, odczytu plików JSON itp.

Jednym sprytnym sposobem, o którym jeszcze nie wspomniano, jest to, jak to się dzieje w bibliotece standardowych rozszerzeń biblioteki Ruby Facets . Od tutaj :

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

Istnieje już Hash#each_pairalias Hash#each. Więc po tej łatce mamy ją Array#each_pairi możemy używać zamiennie do iteracji zarówno skrótów, jak i tablic. To naprawia zaobserwowane szaleństwo PO, które Array#each_with_indexodwraca argumenty blokowe w porównaniu do Hash#each. Przykładowe użycie:

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
źródło