Apa cara "benar" untuk beralih melalui array di Ruby?


341

PHP, untuk semua kutilnya, cukup bagus dalam hal ini. Tidak ada perbedaan antara array dan hash (mungkin saya naif, tapi ini tampaknya benar bagi saya), dan untuk beralih melalui Anda hanya melakukan

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

Di Ruby ada banyak cara untuk melakukan hal semacam ini:

array.length.times do |i|
end

array.each

array.each_index

for i in array

Hash lebih masuk akal, karena saya selalu menggunakan

hash.each do |key, value|

Mengapa saya tidak bisa melakukan ini untuk array? Jika saya ingin mengingat hanya satu metode, saya rasa saya bisa menggunakan each_index(karena itu membuat indeks dan nilai tersedia), tapi itu menjengkelkan harus dilakukan, array[index]bukan hanya value.


Oh benar, saya lupa array.each_with_index. Namun, yang satu ini menyebalkan karena ia pergi |value, key|dan hash.eachpergi |key, value|! Apakah ini tidak gila?


1
Saya kira array#each_with_indexmenggunakan |value, key|karena nama metode menyiratkan urutan, sedangkan urutan yang digunakan untuk hash#eachmeniru hash[key] = valuesintaks?
Benjineer

3
Jika Anda baru saja memulai dengan loop di Ruby, maka periksa menggunakan memilih, menolak, mengumpulkan, menyuntikkan dan mendeteksi
Matthew Carriere

Jawaban:


560

Ini akan beralih melalui semua elemen:

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

Cetakan:

1
2
3
4
5
6

Ini akan mengulangi semua elemen yang memberi Anda nilai dan indeks:

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

Cetakan:

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

Saya tidak yakin dari pertanyaan Anda yang mana yang Anda cari.


1
Di luar topik, saya tidak dapat menemukan each_with_index di ruby ​​1.9 dalam array
CantGetANick

19
@CantGetANick: Kelas Array termasuk modul Enumerable yang memiliki metode ini.
xuinkrbin.

88

Saya pikir tidak ada cara yang benar . Ada banyak cara berbeda untuk beralih, dan masing-masing memiliki ceruk tersendiri.

  • each cukup untuk banyak penggunaan, karena saya tidak sering peduli dengan indeks.
  • each_ with _index bertindak seperti masing-masing Hash # - Anda mendapatkan nilai dan indeks.
  • each_index- hanya indeks. Saya tidak sering menggunakan ini. Setara dengan "length.times".
  • map adalah cara lain untuk beralih, berguna saat Anda ingin mengubah satu array menjadi yang lain.
  • select adalah iterator untuk digunakan ketika Anda ingin memilih subset.
  • inject berguna untuk menghasilkan jumlah atau produk, atau mengumpulkan hasil tunggal.

Mungkin sepertinya banyak yang harus diingat, tetapi jangan khawatir, Anda bisa bertahan tanpa mengetahui semuanya. Tetapi ketika Anda mulai belajar dan menggunakan metode yang berbeda, kode Anda akan menjadi lebih bersih dan lebih jelas, dan Anda akan menuju penguasaan Ruby.


jawaban bagus! Saya ingin menyebutkan yang sebaliknya seperti #reject dan alias seperti #collect.
Emily Tui Ch

60

Saya tidak mengatakan bahwa Array-> |value,index|dan Hash-> |key,value|tidak gila (lihat komentar Horace Loeb), tetapi saya mengatakan bahwa ada cara yang waras untuk mengharapkan pengaturan ini.

Ketika saya berurusan dengan array, saya fokus pada elemen-elemen dalam array (bukan indeks karena indeks bersifat sementara). Metode ini masing-masing dengan indeks, yaitu setiap indeks +, atau | masing-masing, indeks |, atau |value,index|. Ini juga konsisten dengan indeks yang dilihat sebagai argumen opsional, misalnya | nilai | setara dengan | nilai, indeks = nihil | yang konsisten dengan | nilai, indeks |.

Ketika saya berurusan dengan hash, saya sering lebih fokus pada kunci daripada nilai, dan saya biasanya berurusan dengan kunci dan nilai dalam urutan itu, baik key => valueatau hash[key] = value.

Jika Anda ingin mengetik bebek, maka baik secara eksplisit menggunakan metode yang ditentukan seperti yang ditunjukkan Brent Longborough, atau metode implisit seperti yang ditunjukkan maxhawkins.

Ruby adalah tentang mengakomodasi bahasa yang sesuai dengan programmer, bukan tentang mengakomodasi programmer yang sesuai dengan bahasa. Inilah mengapa ada banyak cara. Ada begitu banyak cara untuk memikirkan sesuatu. Di Ruby, Anda memilih yang terdekat dan sisa kode biasanya keluar dengan sangat rapi dan ringkas.

Adapun pertanyaan awal, "Apa cara" benar "untuk beralih melalui array di Ruby?", Well, saya pikir cara inti (yaitu tanpa gula sintaksis yang kuat atau daya berorientasi objek) adalah untuk melakukan:

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

Tapi Ruby adalah tentang gula sintaksis yang kuat dan kekuatan berorientasi objek, tapi bagaimanapun, ini setara dengan hash, dan kunci dapat dipesan atau tidak:

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

Jadi, jawaban saya adalah, "Cara" benar "untuk beralih melalui array di Ruby tergantung pada Anda (yaitu programmer atau tim pemrograman) dan proyek.". Programmer Ruby yang lebih baik membuat pilihan yang lebih baik (yang kekuatan sintaksis dan / atau pendekatan berorientasi objek). Programmer Ruby yang lebih baik terus mencari lebih banyak cara.


Sekarang, saya ingin mengajukan pertanyaan lain, "Apa cara" benar "untuk beralih melalui Range di Ruby ke belakang?"! (Pertanyaan ini adalah bagaimana saya sampai di halaman ini.)

Sangat menyenangkan untuk melakukan (untuk ke depan):

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

tapi saya tidak suka melakukan (untuk mundur):

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

Yah, saya sebenarnya tidak keberatan melakukan itu terlalu banyak, tetapi ketika saya mengajar akan mundur, saya ingin menunjukkan kepada siswa saya simetri yang bagus (yaitu dengan perbedaan minimal, misalnya hanya menambahkan kebalikan, atau langkah -1, tetapi tanpa memodifikasi hal lain). Anda dapat melakukan (untuk simetri):

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

dan

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

yang saya tidak suka banyak, tetapi Anda tidak bisa melakukannya

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

Anda akhirnya bisa melakukannya

class Range

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

end

tapi saya ingin mengajar Ruby murni daripada pendekatan berorientasi objek (dulu). Saya ingin mengulangi:

  • tanpa membuat array (pertimbangkan 0..1000000000)
  • bekerja untuk Rentang apa pun (mis. String, bukan hanya Bilangan Bulat)
  • tanpa menggunakan kekuatan berorientasi objek tambahan (yaitu tidak ada modifikasi kelas)

Saya percaya ini tidak mungkin tanpa mendefinisikan predmetode, yang berarti memodifikasi kelas Range untuk menggunakannya. Jika Anda dapat melakukan ini, beri tahu saya, jika tidak, konfirmasi ketidakmungkinan akan dihargai meskipun itu akan mengecewakan. Mungkin Ruby 1.9 membahas ini.

(Terima kasih atas waktu Anda membaca ini.)


5
1.upto(10) do |i| puts i enddan 10.downto(1) do puts i endjenis memberikan simetri yang Anda inginkan. Semoga itu bisa membantu. Tidak yakin apakah itu akan berfungsi untuk string dan semacamnya.
Suren

(1..10) .to_a.sort {| x, y | y <=> x} .each {| i | menempatkan "i = # {i}"} - mundur lebih lambat.
Davidslv

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

19

Gunakan each_with_index saat Anda membutuhkan keduanya.

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

12

Jawaban lainnya baik-baik saja, tetapi saya ingin menunjukkan satu hal lain: Array dipesan, sedangkan Hash tidak dalam 1,8. (Di Ruby 1.9, Hash dipesan dengan urutan penyisipan kunci.) Jadi, tidak masuk akal sebelum 1.9 untuk beralih ke Hash dengan cara / urutan yang sama seperti Array, yang selalu memiliki pemesanan yang pasti. Saya tidak tahu apa urutan default untuk array asosiatif PHP (rupanya google fu saya juga tidak cukup kuat untuk mengetahuinya), tetapi saya tidak tahu bagaimana Anda dapat mempertimbangkan array PHP reguler dan array asosiatif PHP untuk menjadi "sama" dalam konteks ini, karena urutan untuk array asosiatif tampaknya tidak terdefinisi.

Karena itu, cara Ruby tampak lebih jelas dan intuitif bagi saya. :)


1
hash dan array adalah hal yang sama! array peta integer ke objek dan hashes objek peta ke objek. array hanya kasus khusus hash, bukan?
Tom Lehman

1
Seperti yang saya katakan, array adalah set yang diurutkan. Pemetaan (dalam arti umum) tidak terurut. Jika Anda membatasi set kunci ke bilangan bulat (seperti dengan array), kebetulan bahwa set kunci memiliki pesanan. Dalam pemetaan generik (Hash / Associative Array), kunci mungkin tidak memiliki pesanan.
Pistos

@Horace: Hash dan array tidak sama. Jika salah satu adalah acase khusus dari yang lain, mereka tidak dapat sama. Tetapi lebih buruk lagi, array bukan jenis hash khusus, itu hanya cara abstrak untuk melihatnya. Seperti yang ditunjukkan Brent di atas, penggunaan hash dan array secara bergantian mungkin mengindikasikan bau kode.
Zane

9

Mencoba melakukan hal yang sama secara konsisten dengan array dan hash mungkin hanya bau kode, tetapi, dengan risiko saya dicap sebagai pembuat patch-setengah monyet, jika Anda mencari perilaku yang konsisten, akankah ini berhasil? ?:

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

Berikut adalah empat opsi yang tercantum dalam pertanyaan Anda, yang diatur oleh kebebasan kontrol. Anda mungkin ingin menggunakan yang berbeda tergantung pada apa yang Anda butuhkan.

  1. Cukup melalui nilai-nilai:

    array.each
  2. Cukup telusuri indeks:

    array.each_index
  3. Pergi melalui indeks + variabel indeks:

    for i in array
  4. Kontrol jumlah loop + variabel indeks:

    array.length.times do | i |

5

Saya sudah mencoba membangun menu (di Camping dan Markaby ) menggunakan hash.

Setiap item memiliki 2 elemen: label menu dan URL , jadi hash tampak benar, tetapi URL '/' untuk 'Home' selalu muncul terakhir (seperti yang Anda harapkan untuk hash), sehingga item menu muncul dengan cara yang salah memesan.

Menggunakan array dengan each_slicemelakukan pekerjaan:

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

Menambahkan nilai ekstra untuk setiap item menu (mis. Seperti nama ID CSS ) hanya berarti meningkatkan nilai slice. Jadi, seperti hash tetapi dengan grup yang terdiri dari sejumlah item. Sempurna.

Jadi ini hanya untuk mengucapkan terima kasih karena secara tidak sengaja mengisyaratkan solusi!

Jelas, tetapi patut dinyatakan: Saya sarankan memeriksa apakah panjang array dapat dibagi dengan nilai slice.


4

Jika Anda menggunakan mixin enumerable (seperti halnya Rails), Anda dapat melakukan sesuatu yang mirip dengan cuplikan php yang tercantum. Cukup gunakan metode each_slice dan ratakan 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}" }

Diperlukan sedikit penambalan monyet.

Namun, ini menyebabkan masalah ketika Anda memiliki array rekursif atau hash dengan nilai array. Dalam ruby ​​1.9 masalah ini diselesaikan dengan parameter ke metode rata yang menentukan seberapa dalam untuk recurse.

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

Adapun pertanyaan apakah ini bau kode, saya tidak yakin. Biasanya ketika saya harus membungkuk ke belakang untuk mengulangi sesuatu yang saya melangkah mundur dan menyadari saya salah menyerang masalah.


2

Cara yang benar adalah yang paling nyaman bagi Anda dan melakukan apa yang Anda inginkan. Dalam pemrograman jarang ada satu cara 'benar' untuk melakukan sesuatu, lebih sering ada beberapa cara untuk memilih.

Jika Anda merasa nyaman dengan cara tertentu dalam melakukan sesuatu, lakukan saja, kecuali itu tidak berhasil - maka inilah saatnya untuk menemukan cara yang lebih baik.


2

Di Ruby 2.1, metode each_with_index dihapus. Sebagai gantinya, Anda dapat menggunakan each_index

Contoh:

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

menghasilkan:

0 -- 1 -- 2 --

4
Ini tidak benar. Sebagaimana dinyatakan dalam komentar dari jawaban yang diterima, alasan each_with_indextidak muncul dalam dokumentasi adalah karena disediakan oleh Enumerablemodul.
ihaztehcodez

0

Menggunakan metode yang sama untuk iterasi melalui kedua array dan hash masuk akal, misalnya untuk memproses struktur hash dan array bersarang sering dihasilkan dari parser, dari membaca file JSON dll.

Salah satu cara cerdas yang belum disebutkan adalah bagaimana hal itu dilakukan di pustaka Ruby Facets dari ekstensi pustaka standar. Dari sini :

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

Sudah ada Hash#each_pairalias alias Hash#each. Jadi setelah tambalan ini, kami juga memiliki Array#each_pairdan dapat menggunakannya secara bergantian untuk beralih melalui Hash dan Array. Ini memperbaiki kegilaan yang diamati OP yang Array#each_with_indexmemiliki argumen blok terbalik dibandingkan denganHash#each . Contoh penggunaan:

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, !"
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.