Kể về phần cuối của một vòng lặp .each bằng ruby


91

Nếu tôi có một vòng lặp chẳng hạn như

users.each do |u|
  #some code
end

Nơi người dùng là một băm của nhiều người dùng. Logic có điều kiện dễ dàng nhất để xem nếu bạn đang ở người dùng cuối cùng trong hàm băm người dùng và chỉ muốn thực thi mã cụ thể cho người dùng cuối cùng đó, như vậy

users.each do |u|
  #code for everyone
  #conditional code for last user
    #code for the last user
  end
end

Bạn thực sự có nghĩa là một băm? Đặt hàng trên một hàm băm không phải lúc nào cũng đáng tin cậy (tùy thuộc vào cách bạn thêm mọi thứ vào hàm băm và phiên bản ruby ​​bạn đang sử dụng). Với cách đặt hàng không đáng tin cậy, mục 'cuối cùng' sẽ không nhất quán. Mã trong câu hỏi và câu trả lời bạn nhận được phù hợp hơn với một mảng hoặc có thể liệt kê. Ví dụ, hàm băm không có each_with_indexhoặc lastphương thức.
Shadwell

1
Hashkết hợp trong Enumerable, vì vậy nó có each_with_index. Ngay cả khi các khóa băm không được sắp xếp, loại logic này xuất hiện mọi lúc khi hiển thị các chế độ xem, trong đó mục cuối cùng có thể được hiển thị khác nhau bất kể nó có thực sự là "cuối cùng" theo bất kỳ ý nghĩa dữ liệu nào hay không.
Raphomet

Tất nhiên, hoàn toàn đúng, băm có each_with_index, xin lỗi. Đúng, tôi có thể thấy rằng nó sẽ xuất hiện; chỉ đang cố gắng làm rõ câu hỏi. Cá nhân câu trả lời tốt nhất cho tôi là sử dụng .lastnhưng điều đó không áp dụng cho một mảng chỉ băm.
Shadwell

Bản sao của chỉ số đầu tiên và cuối cùng của Magic trong một vòng lặp trong Ruby / Rails? , ngoài việc đây là một hàm băm chứ không phải là một mảng.
Andrew Grimm,

1
@Andrew đồng ý rằng nó hoàn toàn liên quan, tuy nhiên câu trả lời nhỏ tuyệt vời ít ỏi cho thấy nó không chính xác là một bản dupe.
Sam Saffron

Câu trả lời:


147
users.each_with_index do |u, index|
  # some code
  if index == users.size - 1
    # code for the last user
  end
end

4
Vấn đề với đây là một điều kiện được chạy qua mỗi lần. sử dụng .lastbên ngoài vòng lặp.
bcackerman

3
Tôi chỉ muốn thêm rằng nếu bạn đang iterating qua một hash, bạn phải viết nó như:users.each_with_index do |(key, value), index| #your code end
HUB

Tôi biết nó không phải là hoàn toàn cùng một câu hỏi, nhưng mã này hoạt động tốt hơn tôi nghĩ rằng nếu bạn muốn làm điều gì đó cho mọi người nhưng người sử dụng cuối cùng, vì vậy tôi upvoting vì đó là những gì tôi đang tìm kiếm
hổ trắng

38

Nếu đó là một trong hai / hoặc tình huống, trong đó bạn đang áp dụng một số mã cho tất cả trừ người dùng cuối cùng và sau đó là một số mã duy nhất cho chỉ người dùng cuối cùng, một trong những giải pháp khác có thể phù hợp hơn.

Tuy nhiên, dường như bạn đang chạy cùng một mã cho tất cả người dùng và một số mã bổ sung cho người dùng cuối cùng. Nếu đúng như vậy, điều này có vẻ đúng hơn và nói rõ hơn ý định của bạn:

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

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

2
+1 vì không cần điều kiện, nếu điều đó phù hợp. (tất nhiên, tôi đã làm ở đây)
Jeremy

@meagar sẽ không lừa người dùng lặp lại hai lần?
ant

@ant Không, có một vòng lặp và một lệnh gọi .lastmà không liên quan gì đến lặp.
Mudgar

@meagar để đến cuối cùng nó không lặp lại nội bộ cho đến phần tử cuối cùng? nó có cách nào để truy cập phần tử trực tiếp mà không cần lặp lại?
ant

1
@ant Không, không có vòng lặp nội bộ liên quan .last. Bộ sưu tập đã được khởi tạo, nó chỉ là một truy cập đơn giản của một mảng. Ngay cả khi bộ sưu tập chưa được tải (như trong, nó vẫn là một quan hệ ActiveRecord chưa được lọc) lastvẫn không bao giờ lặp lại để lấy giá trị cuối cùng, điều đó sẽ cực kỳ kém hiệu quả. Nó chỉ đơn giản là sửa đổi truy vấn SQL để trả về bản ghi cuối cùng. Điều đó nói rằng, bộ sưu tập này đã được tải bởi .each, vì vậy không có nhiều phức tạp liên quan hơn nếu bạn đã làm x = [1,2,3]; x.last.
Mudgar

20

Tôi nghĩ cách tiếp cận tốt nhất là:

users.each do |u|
  #code for everyone
  if u.equal?(users.last)
    #code for the last user
  end
end

22
Vấn đề với câu trả lời này là nếu người dùng cuối cùng cũng xuất hiện sớm hơn trong danh sách, thì mã điều kiện sẽ được gọi nhiều lần.
evanrmurphy

1
bạn phải sử dụng u.equal?(users.last), equal?phương thức so sánh object_id, không phải giá trị của đối tượng. Nhưng điều này sẽ không hoạt động với các ký hiệu và số.
Nafaa Boutefer,

11

Bạn đã thử each_with_indexchưa?

users.each_with_index do |u, i|
  if users.size-1 == i
     #code for last items
  end
end

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

3

Đôi khi tôi thấy tốt hơn nên tách logic thành hai phần, một cho tất cả người dùng và một cho phần cuối cùng. Vì vậy, tôi sẽ làm một cái gì đó như thế này:

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

method_for_all_users users.last
method_for_last_user users.last

3

Bạn cũng có thể sử dụng cách tiếp cận của @ crazyger cho một trong hai / hoặc tình huống, trong đó bạn đang áp dụng một số mã cho tất cả trừ người dùng cuối cùng và sau đó là một số mã duy nhất cho chỉ người dùng cuối cùng.

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

Bằng cách này bạn không cần điều kiện!


@BeniBela Không, [-1] là phần tử cuối cùng. Không có [-0]. Vì vậy, thứ hai đến cuối cùng là [-2].
coderuby

users[0..-2]là đúng, như là users[0...-1]. Lưu ý các nhà khai thác phạm vi khác nhau ..vs ..., xem stackoverflow.com/a/9690992/67834
Eliot Sykes

2

Một giải pháp khác là giải cứu khỏi StopIteration:

user_list = users.each

begin
  while true do
    user = user_list.next
    user.do_something
  end
rescue StopIteration
  user.do_something
end

4
Đây không phải là một giải pháp tốt cho vấn đề này. Không nên lạm dụng các ngoại lệ để kiểm soát luồng đơn giản, chúng dành cho các tình huống đặc biệt .
Mudgar

@meagar Điều này thực sự gần như khá tuyệt. Tôi đồng ý rằng unrescued ngoại lệ hoặc những người mà cần phải được chuyển đến một phương pháp cao hơn chỉ nên cho các tình huống đặc biệt. Tuy nhiên, đây là một cách gọn gàng (và dường như là duy nhất) để truy cập vào kiến ​​thức gốc của ruby ​​về nơi kết thúc của trình lặp. Nếu có một cách khác, tất nhiên, được ưu tiên. Vấn đề thực sự duy nhất tôi gặp phải là nó dường như bỏ qua và không làm gì cho mục đầu tiên. Khắc phục điều đó sẽ đưa nó vượt qua ranh giới của sự bừa bộn.
Adamantish

2
@meagar Xin lỗi vì "lập luận từ cơ quan có thẩm quyền", nhưng Matz không đồng ý với bạn. Trong thực tế, StopIterationđược thiết kế vì lý do chính xác để xử lý các lần thoát vòng lặp. Từ cuốn sách của Matz: "Điều này có vẻ bất thường - một ngoại lệ được đưa ra cho một điều kiện kết thúc dự kiến ​​hơn là một sự kiện bất ngờ và ngoại lệ. ( StopIterationLà hậu duệ của StandardErrorIndexError; lưu ý rằng nó là một trong những lớp ngoại lệ duy nhất không có từ "Lỗi" trong tên của nó.) Ruby tuân theo Python trong kỹ thuật lặp lại bên ngoài này. (Thêm ...)
BobRodes

(...) Bằng cách coi việc kết thúc vòng lặp là một ngoại lệ, nó làm cho logic lặp của bạn cực kỳ đơn giản; không cần phải kiểm tra giá trị trả về của nextmột giá trị kết thúc lần lặp đặc biệt và không cần gọi một số loại next?vị từ trước khi gọi next. "
BobRodes

1
@Adamantish Cần lưu ý rằng loop docó một ẩn rescuekhi gặp phải StopIteration; nó được sử dụng đặc biệt khi lặp lại bên ngoài một Enumeratorđối tượng. loop do; my_enum.next; endsẽ lặp lại my_enumvà thoát ra khi kết thúc; không cần phải đặt rescue StopIterationở đó. (Bạn phải làm nếu bạn sử dụng whilehoặc until.)
BobRodes

0

Không có phương pháp cuối cùng để băm cho một số phiên bản của 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

Nó có phải là một câu trả lời cải thiện câu trả lời của người khác? Vui lòng đăng câu trả lời nào đề cập đến phương pháp 'cuối cùng' và bạn đề xuất gì để khắc phục vấn đề này.
Artemix

Đối với các phiên bản Ruby không có a last, bộ khóa sẽ không có thứ tự, vì vậy câu trả lời này sẽ trả về kết quả sai / ngẫu nhiên.
Mudgar
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.