Ruby'de bir diziyi yinelemenin “doğru” yolu nedir?


341

PHP, tüm siğiller için, bu sayımda oldukça iyidir. Bir dizi ve bir karma arasında bir fark yoktur (belki de naifim, ama bu benim için açıkça doğru görünüyor) ve her ikisini de yinelemek için

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

Ruby'de bu tür şeyleri yapmanın birkaç yolu vardır:

array.length.times do |i|
end

array.each

array.each_index

for i in array

Her zaman kullandığım için karmalar daha mantıklı

hash.each do |key, value|

Bunu neden diziler için yapamıyorum? Ben sadece bir yöntemi hatırlamak istiyorum, ben kullanabilirsiniz each_index(hem dizin ve değer kullanılabilir yapar çünkü), ama array[index]sadece yerine yapmak zorunda can sıkıcı value.


Ah doğru, unuttum array.each_with_index. Ancak, bu berbat çünkü gidiyor |value, key|ve hash.eachgidiyor |key, value|! Bu deli değil mi?


1
Yöntem adı sipariş anlamına gelir, çünkü sözdizimi için kullanılan sipariş sözdizimi taklit ettiği için array#each_with_indexkullanır sanırım ? |value, key|hash#eachhash[key] = value
Benjineer

3
Ruby'de döngülere yeni başlıyorsanız, select, reddet, topla, enjekte et ve
Matthew Carriere

Yanıtlar:


560

Bu, tüm unsurları tekrarlayacaktır:

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

Baskılar:

1
2
3
4
5
6

Bu, size değeri ve dizini veren tüm öğeleri yineleyecektir:

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

Baskılar:

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

Hangisini aradığınız sorusundan tam olarak emin değilim.


1
Konu Dışı Ruby 1.9 dizisi içinde every_with_index bulamıyorum
CantGetANick

19
@CantGetANick: Array sınıfı, bu yönteme sahip Enumerable modülünü içerir.
xuinkrbin.

88

Bence tek bir doğru yol yok. Yinelemenin birçok farklı yolu vardır ve her birinin kendi nişi vardır.

  • each çoğu kullanım için yeterli, çünkü çoğu zaman dizinleri umursamıyorum.
  • each_ with _index Hash # gibi davranır - değeri ve dizini alırsınız.
  • each_index- sadece dizinler. Bunu sık kullanmıyorum. "Length.times" ile eşdeğerdir.
  • map yinelemenin başka bir yoludur, bir diziyi diğerine dönüştürmek istediğinizde yararlıdır.
  • select bir alt küme seçmek istediğinizde kullanılacak yineleyicidir.
  • inject toplamlar veya ürünler oluşturmak veya tek bir sonuç toplamak için kullanışlıdır.

Hatırlanması gereken bir şey gibi görünebilir, ancak endişelenmeyin, hepsini bilmeden alabilirsiniz. Ancak, farklı yöntemleri öğrenmeye ve kullanmaya başladığınızda, kodunuz daha temiz ve daha açık hale gelir ve Ruby ustalığına doğru yola çıkacaksınız.


mükemmel cevap! #Reject gibi tersi ve #collect gibi takma adlardan bahsetmek istiyorum.
Emily Tui Ch

60

Ben Array-> |value,index|ve Hash-> |key,value|deli değil demiyorum (Horace Loeb'in yorumuna bakın), ama bu düzenlemeyi beklemenin mantıklı bir yolu olduğunu söylüyorum.

Dizilerle uğraşırken dizideki öğelere odaklanıyorum (dizin geçici olduğu için dizine değil). Yöntem her biri indeks, yani her bir + dizin veya | her, dizin | veya |value,index|. Bu aynı zamanda dizin isteğe bağlı bir bağımsız değişken olarak görüntüleniyor, örn. | Value | | değer, index = nil | | değer, dizin | ile tutarlı olan.

Ben karmaları ile uğraşıyorum, ben çoğu zaman daha değerlerinden daha tuşların üzerinde duruldu ediyorum, ve ben genellikle anahtar ve bu sırayla değerlere ya uğraşıyorum key => valueya hash[key] = value.

Ördek yazmayı istiyorsanız, Brent Longborough'un gösterdiği gibi tanımlanmış bir yöntemi veya maxhawkins'in gösterdiği gibi örtük bir yöntemi kullanın.

Ruby, dili programcıya uyacak şekilde yerleştirmekle ilgilidir, dile uyacak programcı hakkında değildir. Bu yüzden bu kadar çok yol var. Bir şeyi düşünmenin pek çok yolu var. Ruby'de en yakını seçersiniz ve kodun geri kalanı genellikle son derece düzgün ve kısaca düşer.

Orijinal soruya gelince, "Ruby'de bir dizi üzerinden yinelemenin" doğru "yolu nedir?"

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

Ancak Ruby tamamen güçlü sözdizimsel şeker ve nesne yönelimli güçle ilgilidir, ancak burada yine de karmalar için eşdeğerdir ve anahtarlar sipariş edilebilir veya edilmeyebilir:

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

Yani, cevabım, "Ruby'deki bir dizi üzerinden tekrarlamanın" doğru "yolu size (yani programcıya veya programlama ekibine) ve projeye bağlıdır." Daha iyi Ruby programcısı daha iyi bir seçim yapar (hangisinin sözdizimsel gücü ve / veya hangi nesne yönelimli yaklaşım). Daha iyi Ruby programcısı daha fazla yol aramaya devam eder.


Şimdi başka bir soru daha sormak istiyorum: "Ruby'deki bir Range boyunca geriye doğru“ doğru ”yol nedir?”! (Bu soru bu sayfaya nasıl geldiğimdir.)

Bunu yapmak güzel (forvetler için):

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

ama (geriye doğru) yapmak istemiyorum:

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

Aslında bunu çok fazla yapmayı umursamıyorum, ancak geriye doğru gitmeyi öğrettiğimde, öğrencilerime hoş bir simetri göstermek istiyorum (yani minimum farkla, örneğin sadece bir ters veya adım -1 eklemek, ancak başka bir şeyi değiştirmek). Şunları yapabilirsiniz (simetri için):

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

ve

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

ki çok sevmiyorum ama yapamazsın

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

Sonuçta yapabilirsin

class Range

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

end

ama nesne yönelimli yaklaşımlardan ziyade saf Ruby öğretmek istiyorum (henüz). Geriye doğru tekrarlamak istiyorum:

  • bir dizi oluşturmadan (0..1000000000'i düşünün)
  • herhangi bir Aralık için çalışma (ör. yalnızca Tamsayılar değil, Dizeler)
  • herhangi bir ekstra nesne yönelimli güç kullanmadan (yani sınıf değişikliği olmadan)

Bunun bir predyöntem tanımlanmadan imkansız olduğuna inanıyorum ; Bunu yapabilirseniz lütfen bana bildirin, aksi takdirde hayal kırıklığı yaratacağı halde imkansızlığın teyidi takdir edilecektir. Belki Ruby 1.9 buna hitap ediyor.

(Bunu okuduğunuz için teşekkür ederiz.)


5
1.upto(10) do |i| puts i endve 10.downto(1) do puts i endistediğiniz simetriyi verir. Umarım yardımcı olur. Dizeler ve benzeri için işe yarayıp yaramayacağından emin değilim.
Suren

(1..10) .to_a.sort {| x, y | y <=> x} .each {| i | "i = # {i}"} koyar - tersi daha yavaştır.
Davidslv

Yapabilirsin [*1..10].each{|i| puts "i=#{i}" }.
Hauleth

19

Her ikisine de ihtiyacınız olduğunda each_with_index komutunu kullanın.

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

12

Diğer cevaplar gayet iyi, ama başka bir çevresel noktaya işaret etmek istedim: Diziler sıralanırken, Hashes 1.8'de değil. (Ruby 1.9'da Hashes, anahtarların eklenme sırasına göre sıralanır.) Bu nedenle 1.9'dan önce, her zaman belirli bir sıralamaya sahip olan Dizilerle aynı şekilde / dizide bir Hash'i yinelemek mantıklı olmazdı. Ben PHP ilişkisel diziler için varsayılan sipariş ne olduğunu bilmiyorum (görünüşe göre benim google fu, ya da anlamaya yeterince güçlü değil), ama düzenli PHP dizileri ve PHP ilişkisel diziler düşünebilirsiniz nasıl bilmiyorum çağrışımsal diziler sırası tanımsız göründüğü için bu bağlamda "aynı" olmalıdır.

Bu nedenle, Ruby yolu benim için daha açık ve sezgisel görünüyor. :)


1
karmalar ve diziler aynı şeydir! diziler eşleme tam sayılarını nesnelere, eşleme eşlemelerini de nesnelere eşleştirir. diziler sadece özel hash vakaları, değil mi?
Tom Lehman

1
Dediğim gibi, bir dizi sıralı bir kümedir. Bir eşleme (genel anlamda) sırasızdır. Anahtar kümesini tamsayılarla (dizilerde olduğu gibi) kısıtlarsanız, anahtar kümesinin bir sırası olur. Genel bir eşlemede (Hash / Associative Array), anahtarların bir sırası olmayabilir.
Pistos

@Horace: Karmalar ve diziler aynı değil. Biri diğerinin özel bir temeli ise, aynı olamaz. Ama daha da kötüsü, diziler özel bir karma türü değildir, bu onları izlemenin yalnızca soyut bir yoludur. Yukarıda Brent'in işaret ettiği gibi, karma ve dizileri birbirinin yerine kullanmak bir kod kokusunu gösterebilir.
Zane

9

Diziler ve karmaları ile tutarlı bir şekilde aynı şeyi yapmaya çalışıyor olabilir tutarlı davranış arıyorsanız, bir codorous yarım maymun-yamalı olarak markalı varlığımın riski, tıpkı bir kod koku olabilir, ancak bu bir hile ?:

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

Sorunuzda listelenen ve kontrol özgürlüğüne göre düzenlenmiş dört seçenek. İhtiyacınıza bağlı olarak farklı bir tane kullanmak isteyebilirsiniz.

  1. Sadece değerleri gözden geçirin:

    array.each
  2. Sadece endeksleri gözden geçirin:

    array.each_index
  3. Endeksler + dizin değişkeni üzerinden git:

    for i in array
  4. Kontrol döngüsü sayısı + dizin değişkeni:

    array.length.times do | i |

5

Bir karma ( Camping ve Markaby içinde ) bir karma kullanarak bir menü oluşturmaya çalışıyordum .

Her öğenin 2 öğesi vardır: bir menü etiketi ve bir URL , bu yüzden bir karma doğru görünüyordu, ancak 'Ev' için '/' URL her zaman en son göründü (bir karma beklediğiniz gibi), bu yüzden menü öğeleri yanlış görünüyordu sipariş.

İle bir dizi kullanmak each_sliceiş yapar:

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

Her menü öğesi için ekstra değerler eklemek (örneğin, bir CSS Kimliği adı gibi) sadece dilim değerini artırmak anlamına gelir. Yani, bir karma gibi ama çok sayıda öğeden oluşan gruplarla. Mükemmel.

Yani bu sadece bir çözümü yanlışlıkla ima ettiğiniz için teşekkür etmek için!

Açık, ama belirtmeye değer: Dizinin uzunluğunun dilim değeri ile bölünebilir olup olmadığını kontrol etmenizi öneririm.


4

Eğer kullanırsanız enumerable (Raylar yaptığı gibi) mixin Listelenen php pasajı benzer bir şey yapabilirsiniz. Sadece each_slice yöntemini kullanın ve karmayı düzleştirin.

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

Daha az maymun yaması gerekir.

Ancak, özyinelemeli bir dizi veya dizi değerlerine sahip bir karma varsa sorunlara neden olur. Yakut 1.9'da bu sorun yassılaştırma yönteminin ne kadar derinlemesine çekileceğini belirleyen bir parametre ile çözülür.

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

Bu bir kod kokusu olup olmadığı sorusuna gelince, emin değilim. Genellikle bir şey üzerinde tekrarlamak için geriye doğru eğilmek zorunda kaldığımda, geri adım atıp soruna yanlış saldırdığımı fark ettim.


2

Doğru yol, kendinizi en rahat hissettiğiniz ve ne yapmak istediğinizi yapar. Programlamada nadiren bir şeyleri yapmak için 'doğru' bir yol vardır, daha sıklıkla seçmenin birden fazla yolu vardır.

Bazı şeyleri yapmaktan rahatsanız, işe yaramazsa sadece yapın - o zaman daha iyi bir yol bulma zamanı.


2

Ruby 2.1'de, each_with_index yöntemi kaldırılır. Bunun yerine each_index'i kullanabilirsiniz

Misal:

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

üretir:

0 -- 1 -- 2 --

4
Bu doğru değil. Kabul edilen cevabın yorumlarında belirtildiği gibi each_with_index, dokümantasyonda görünmeyen neden Enumerablemodül tarafından sağlanmış olmasıdır .
ihaztehcodez

0

Hem diziler hem de karmalar yoluyla yineleme için aynı yöntemi kullanmak, örneğin genellikle ayrıştırıcılardan kaynaklanan JSON dosyaları vb.

Henüz bahsedilmeyen akıllı bir yol , standart kütüphane uzantılarının Ruby Facets kütüphanesinde nasıl yapıldığıdır . Gönderen burada :

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

Zaten Hash#each_pairbir takma adı var Hash#each. Bu yamadan sonra, Array#each_pairhem Hashler hem de Diziler arasında yineleme yapmak için bunu değiştirilebilir ve kullanabiliriz. Bu, OP'nin Array#each_with_indexblok argümanlarına göre tersine çevrilmiş gözlenen deliliğini düzeltir Hash#each. Örnek kullanım:

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, !"
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.