Beri tahu akhir dari lingkaran .each dalam ruby

91

Jika saya memiliki loop seperti

users.each do |u|
  #some code
end

Di mana pengguna adalah hash dari beberapa pengguna. Apa logika kondisional termudah untuk dilihat jika Anda berada di pengguna terakhir dalam hash pengguna dan hanya ingin mengeksekusi kode tertentu untuk pengguna terakhir itu, jadi seperti

users.each do |u|
  #code for everyone
  #conditional code for last user
    #code for the last user
  end
end
Splashlin
sumber
Apakah yang Anda maksud adalah hash? Memesan dengan hash tidak selalu dapat diandalkan (tergantung bagaimana Anda menambahkan sesuatu ke hash dan versi ruby ​​apa yang Anda gunakan). Dengan pemesanan yang tidak dapat diandalkan, item 'terakhir' tidak akan konsisten. Kode dalam pertanyaan dan jawaban yang Anda peroleh lebih sesuai untuk larik atau dapat dihitung. Misalnya, hash tidak memiliki each_with_indexatau lastmetode.
Shadwell
1
Hashbercampur di Enumerable, jadi memang ada each_with_index. Meskipun kunci hash tidak diurutkan, logika semacam ini selalu muncul saat merender tampilan, di mana item terakhir mungkin ditampilkan secara berbeda terlepas dari apakah itu benar-benar "terakhir" dalam arti data yang berarti.
Raphomet
Tentu saja, benar, hash memang punya each_with_index, maaf. Ya, saya dapat melihat bahwa itu akan muncul; hanya mencoba untuk memperjelas pertanyaannya. Secara pribadi jawaban terbaik bagi saya adalah penggunaan .lasttetapi itu tidak berlaku untuk hash hanya sebuah array.
Shadwell
Duplikat Indikator Pertama dan Terakhir Ajaib dalam Loop di Ruby / Rails? , selain ini menjadi hash daripada sebuah array.
Andrew Grimm
1
@Andrew setuju ini benar-benar terkait, tetapi jawaban kecil yang luar biasa menunjukkan bagaimana itu tidak benar-benar menipu.
Sam Saffron

Jawaban:

147
users.each_with_index do |u, index|
  # some code
  if index == users.size - 1
    # code for the last user
  end
end
Raphomet
sumber
4
Masalah dengan ini adalah persyaratan dijalankan setiap waktu. gunakan di .lastluar loop.
bcackerman
3
Saya hanya menambahkan bahwa jika Anda mengulangi hash, Anda harus menulisnya seperti:users.each_with_index do |(key, value), index| #your code end
HUB
Saya tahu ini bukan pertanyaan yang sepenuhnya sama, tetapi kode ini berfungsi lebih baik Saya pikir jika Anda ingin melakukan sesuatu kepada semua orang TETAPI pengguna terakhir, jadi saya meningkatkan karena itulah yang saya cari
WhiteTiger
38

Jika ini adalah situasi salah satu / atau, di mana Anda menerapkan beberapa kode ke semua kecuali pengguna terakhir dan kemudian beberapa kode unik hanya untuk pengguna terakhir, salah satu solusi lain mungkin lebih tepat.

Namun, Anda tampaknya menjalankan kode yang sama untuk semua pengguna, dan beberapa kode tambahan untuk pengguna terakhir. Jika demikian, ini tampaknya lebih benar, dan lebih jelas menyatakan maksud Anda:

users.each do |u|
  #code for everyone
end

users.last.do_stuff() # code for last user
meagar
sumber
2
1 untuk tidak membutuhkan persyaratan, jika itu sesuai. (tentu saja, saya lakukan di sini)
Jeremy
@ Meagar bukankah loop ini akan melewati pengguna dua kali?
semut
@ant Tidak, ada satu loop dan satu panggilan .lastyang tidak ada hubungannya dengan perulangan.
meagar
@ Meagar jadi untuk mendapatkan yang terakhir itu tidak secara internal mengulang sampai elemen terakhir? itu memiliki cara mengakses elemen secara langsung tanpa perulangan?
semut
1
@ant Tidak, tidak ada pengulangan internal yang terlibat di dalamnya .last. Koleksi ini sudah dibuat instance-nya, itu hanya akses sederhana dari sebuah array. Sekalipun koleksi belum dimuat (seperti dalam, itu masih merupakan relasi ActiveRecord yang tidak terhidrasi) lastmasih tidak pernah berulang untuk mendapatkan nilai terakhir, itu akan sangat tidak efisien. Ini hanya mengubah kueri SQL untuk mengembalikan catatan terakhir. .eachMeskipun demikian, koleksi ini telah dimuat oleh , jadi tidak ada kerumitan yang lebih daripada jika Anda melakukannya x = [1,2,3]; x.last.
meagar
20

Menurut saya pendekatan terbaik adalah:

users.each do |u|
  #code for everyone
  if u.equal?(users.last)
    #code for the last user
  end
end
Alter Lagos
sumber
22
Masalah dengan jawaban ini adalah jika pengguna terakhir juga muncul sebelumnya dalam daftar, maka kode bersyarat akan dipanggil beberapa kali.
evanrmurphy
1
Anda harus menggunakan u.equal?(users.last), equal?metode membandingkan object_id, bukan nilai objek. Tetapi ini tidak akan bekerja dengan simbola dan angka.
Nafaa Boutefer
11

Apakah kamu sudah mencobanya each_with_index?

users.each_with_index do |u, i|
  if users.size-1 == i
     #code for last items
  end
end
Teja Kantamneni
sumber
6
h = { :a => :aa, :b => :bb }
h.each_with_index do |(k,v), i|
  puts ' Put last element logic here' if i == h.size - 1
end
DigitalRoss
sumber
3

Terkadang saya merasa lebih baik untuk memisahkan logika menjadi dua bagian, satu untuk semua pengguna dan satu lagi untuk yang terakhir. Jadi saya akan melakukan sesuatu seperti ini:

users[0...-1].each do |user|
  method_for_all_users user
end

method_for_all_users users.last
method_for_last_user users.last
xlembouras.dll
sumber
3

Anda dapat menggunakan pendekatan @ meager juga untuk situasi salah satu / atau, di mana Anda menerapkan beberapa kode ke semua kecuali pengguna terakhir dan kemudian beberapa kode unik hanya untuk pengguna terakhir.

users[0..-2].each do |u|
  #code for everyone except the last one, if the array size is 1 it gets never executed
end

users.last.do_stuff() # code for last user

Dengan cara ini Anda tidak perlu bersyarat!

coderuby
sumber
@BeniBela Tidak, [-1] adalah elemen terakhir. Tidak ada [-0]. Jadi yang kedua dari terakhir adalah [-2].
coderuby
users[0..-2]benar, sebagaimana adanya users[0...-1]. Perhatikan perbedaan operator ..vs ..., lihat stackoverflow.com/a/9690992/67834
Eliot Sykes
2

Solusi lain adalah menyelamatkan dari StopIteration:

user_list = users.each

begin
  while true do
    user = user_list.next
    user.do_something
  end
rescue StopIteration
  user.do_something
end
ricardokrieg.dll
sumber
4
Ini bukan solusi yang bagus untuk masalah ini. Pengecualian tidak boleh disalahgunakan untuk kontrol aliran sederhana, pengecualian untuk situasi luar biasa .
meagar
@meagar Ini sebenarnya cukup keren. Saya setuju bahwa pengecualian yang tidak diselesaikan atau yang perlu diteruskan ke metode yang lebih tinggi seharusnya hanya untuk situasi luar biasa. Ini, bagaimanapun, adalah cara yang rapi (dan tampaknya hanya) untuk mendapatkan akses ke pengetahuan asli Ruby tentang di mana iterator berakhir. Jika ada cara lain, tentu saja lebih disukai. Satu-satunya masalah nyata yang saya ambil dengan ini adalah tampaknya melewatkan dan tidak melakukan apa pun untuk item pertama. Memperbaiki itu akan membuatnya melewati batas ketidakberesan.
Adamantish
2
@meagar Maaf untuk "argumen dari otoritas," tapi Matz tidak setuju dengan Anda. Bahkan, StopIterationdirancang untuk alasan yang tepat dalam menangani keluar loop. Dari buku Matz: "Ini mungkin tampak tidak biasa — pengecualian dimunculkan untuk kondisi penghentian yang diharapkan daripada kejadian yang tidak terduga dan luar biasa. ( StopIterationAdalah keturunan dari StandardErrordan IndexError; perhatikan bahwa ini adalah satu-satunya kelas pengecualian yang tidak memiliki kata "Error" dalam namanya.) Ruby mengikuti Python dalam teknik iterasi eksternal ini. (Selengkapnya ...)
BobRodes
(...) Dengan memperlakukan penghentian loop sebagai pengecualian, itu membuat logika perulangan Anda sangat sederhana; tidak perlu memeriksa nilai kembalian nextuntuk nilai akhir iterasi khusus, dan tidak perlu memanggil semacam next?predikat sebelum menelepon next. "
BobRodes
1
@Adamantish Perlu dicatat bahwa loop domemiliki implisit rescueketika menghadapi a StopIteration; ini secara khusus digunakan saat melakukan iterasi Enumeratorobjek secara eksternal . loop do; my_enum.next; endakan mengulang my_enumdan keluar pada akhirnya; tidak perlu dimasukkan ke rescue StopIterationsana. (Anda harus melakukannya jika Anda menggunakan whileatau until.)
BobRodes
0

Tidak ada metode hash terakhir untuk beberapa versi ruby

h = { :a => :aa, :b => :bb }
last_key = h.keys.last
h.each do |k,v|
    puts "Put last key #{k} and last value #{v}" if last_key == k
end
Sathianarayanan Sundaram
sumber
Apakah itu jawaban yang meningkatkan jawaban orang lain? Silakan posting jawaban mana yang menyebutkan metode 'terakhir' dan apa yang Anda usulkan untuk mengatasi masalah ini.
Artemix
Untuk versi Ruby yang tidak memiliki last, rangkaian kunci tidak akan diurutkan, jadi jawaban ini akan mengembalikan hasil yang salah / acak.
meagar