Mỗi trận đấu với nhau


200

Tôi vừa có một câu hỏi nhanh liên quan đến các vòng lặp trong Ruby. Có sự khác biệt giữa hai cách lặp này thông qua một bộ sưu tập không?

# way 1
@collection.each do |item|
  # do whatever
end

# way 2
for item in @collection
  # do whatever
end

Chỉ tự hỏi nếu những điều này là giống hệt nhau hoặc nếu có thể có một sự khác biệt tinh tế (có thể là khi @collectionkhông).

Câu trả lời:


315

Đây là sự khác biệt duy nhất:

mỗi:

irb> [1,2,3].each { |x| }
  => [1, 2, 3]
irb> x
NameError: undefined local variable or method `x' for main:Object
    from (irb):2
    from :0

cho:

irb> for x in [1,2,3]; end
  => [1, 2, 3]
irb> x
  => 3

Với forvòng lặp, biến lặp vẫn tồn tại sau khi khối được hoàn thành. Với eachvòng lặp, nó không, trừ khi nó đã được xác định là biến cục bộ trước khi vòng lặp bắt đầu.

Ngoài ra, forchỉ là cú pháp đường cho eachphương pháp.

Khi @collectionnilcả hai vòng ném một ngoại lệ:

Ngoại lệ: không xác định biến cục bộ hoặc phương thức `@collection 'cho main: Object


3
Có một lý do chính đáng tại sao x vẫn còn trong trường hợp hoặc thiết kế xấu này: P? Dường như với tôi điều này là khá không trực quan so với hầu hết các ngôn ngữ khác.
cyc115

3
@ cyc115 Lý do tại sao xcòn lại trong cho kịch bản là do thực tế rằng (nói chung) từ khóa không tạo ra phạm vi mới. nếu , trừ khi , bắt đầu , trong , trong khi , vv tất cả đều hoạt động với phạm vi hiện tại. #eachtuy nhiên chấp nhận một khối. Các khối luôn thêm phạm vi riêng của chúng lên trên phạm vi hiện tại. Có nghĩa là khai báo một biến mới trong khối (do đó một phạm vi mới) sẽ không thể truy cập được từ bên ngoài khối do phạm vi bổ sung đó không có sẵn ở đó.
3limin4t0r

43

Xem " Ác ma của vòng lặp " để biết giải thích tốt (có một sự khác biệt nhỏ khi xem xét phạm vi biến).

Sử dụng eachđược coi là sử dụng thành ngữ hơn của Ruby.


@zachlatta: Cảm ơn bạn đã thông báo. Tôi sẽ chỉnh sửa liên kết để trỏ đến một biến thể webarchive.org của bài viết!
BarsheD

1
graysoftinc.com/early-steps/the-evils-of-the-for-loop là liên kết mới, bây giờ trang web của JEG2 đã hoạt động trở lại.
pnomolos

30

Ví dụ đầu tiên của bạn,

@collection.each do |item|
  # do whatever
end

là thành ngữ hơn . Trong khi Ruby hỗ trợ các cấu trúc lặp như forwhile, cú pháp khối thường được ưa thích hơn.

Một sự khác biệt tinh tế khác là bất kỳ biến nào bạn khai báo trong một forvòng lặp sẽ có sẵn bên ngoài vòng lặp, trong khi những biến trong khối lặp là riêng tư một cách hiệu quả.


whileuntilthực sự có một số cách sử dụng cụ thể không thể thay thế bằng mỗi cách sử dụng, ví dụ như tạo các giá trị duy nhất hoặc cho REPL.
tối đa


2

Có vẻ như không có sự khác biệt, forsử dụng eachbên dưới.

$ irb
>> for x in nil
>> puts x
>> end
NoMethodError: undefined method `each' for nil:NilClass
    from (irb):1
>> nil.each {|x| puts x}
NoMethodError: undefined method `each' for nil:NilClass
    from (irb):4

Giống như Bayard nói, mỗi cái đều thành ngữ hơn. Nó ẩn nhiều hơn từ bạn và không yêu cầu các tính năng ngôn ngữ đặc biệt. Nhận xét của mỗi Telemachus

for .. in .. đặt iterator bên ngoài phạm vi của vòng lặp, vì vậy

for a in [1,2]
  puts a
end

ađược xác định sau khi vòng lặp kết thúc. Như eachkhông có. Đó là một lý do khác có lợi cho việc sử dụng each, bởi vì biến temp sống trong một khoảng thời gian ngắn hơn.


1
một sự khác biệt nhỏ (như yjerem, ChristopheD và Bayard đề cập) liên quan đến phạm vi biến.
Telemachus

Không chính xác, forkhông sử dụng eachbên dưới. Xem câu trả lời khác.
akuhn

@akuhn Để làm rõ hơn xin vui lòng xem câu hỏi này và cả câu trả lời tuyệt vời của nó.
Sagar Pandya

2

Không bao giờ sử dụng fornó có thể gây ra lỗi gần như không thể tìm thấy.

Đừng để bị lừa, đây không phải là vấn đề về mã hoặc thành ngữ. Việc thực hiện của Ruby forcó một lỗ hổng nghiêm trọng và không nên được sử dụng.

Đây là một ví dụ forgiới thiệu một lỗi,

class Library
  def initialize
    @ary = []
  end
  def method_with_block(&block)
    @ary << block
  end
  def method_that_uses_these_blocks
    @ary.map(&:call)
  end
end

lib = Library.new

for n in %w{foo bar quz}
  lib.method_with_block { n }
end

puts lib.method_that_uses_these_blocks

Bản in

quz
quz
quz

Sử dụng %w{foo bar quz}.each { |n| ... }bản in

foo
bar
quz

Tại sao?

Trong một forvòng lặp, biến nđược định nghĩa một lần và duy nhất và sau đó một định nghĩa được sử dụng cho tất cả các lần lặp. Do đó, mỗi khối tham chiếu đến cùng nmột giá trị cóquz theo thời gian vòng lặp kết thúc. Bọ cánh cứng!

Trong một eachvòng lặp, một biến mới nđược xác định cho mỗi lần lặp, ví dụ ở trên biến nđược xác định ba lần riêng biệt. Do đó mỗi khối tham chiếu riêng biệt nvới các giá trị chính xác.


0

Theo tôi biết, sử dụng các khối thay vì các cấu trúc điều khiển bằng ngôn ngữ là thành ngữ hơn.


0

Tôi chỉ muốn đưa ra một điểm cụ thể về vòng lặp for trong Ruby. Nó có vẻ giống như một cấu trúc tương tự như các ngôn ngữ khác, nhưng trên thực tế, nó là một biểu thức giống như mọi cấu trúc lặp khác trong Ruby. Trong thực tế, for in hoạt động với các đối tượng Enlessable giống như mỗi iterator.

Bộ sưu tập được truyền cho for có thể là bất kỳ đối tượng nào có mỗi phương thức lặp. Mảng và băm xác định từng phương thức và nhiều đối tượng Ruby khác cũng vậy. Vòng lặp for / in gọi mỗi phương thức của đối tượng đã chỉ định. Khi iterator mang lại các giá trị, vòng lặp for gán từng giá trị (hoặc từng bộ giá trị) cho biến đã chỉ định (hoặc biến) và sau đó thực thi mã trong phần thân.

Đây là một ví dụ ngớ ngẩn, nhưng minh họa điểm mà vòng lặp for hoạt động với bất kỳ đối tượng nào có một phương thức, giống như cách mỗi trình lặp thực hiện:

class Apple
  TYPES = %w(red green yellow)
  def each
    yield TYPES.pop until TYPES.empty?
  end
end

a = Apple.new
for i in a do
  puts i
end
yellow
green
red
=> nil

Và bây giờ mỗi trình vòng lặp:

a = Apple.new
a.each do |i|
  puts i
end
yellow
green
red
=> nil

Như bạn có thể thấy, cả hai đều phản ứng với từng phương thức mang lại giá trị cho khối. Như mọi người ở đây đã nêu, chắc chắn nên sử dụng mỗi vòng lặp qua vòng lặp for. Tôi chỉ muốn lái xe về nhà rằng không có gì kỳ diệu về vòng lặp for. Đó là một biểu thức gọi từng phương thức của một bộ sưu tập và sau đó chuyển nó đến khối mã của nó. Do đó, đây là trường hợp rất hiếm bạn cần sử dụng để nhập. Sử dụng mỗi trình vòng lặp hầu như luôn luôn (với lợi ích bổ sung của phạm vi khối).


0
(1..4).each { |i| 


  a = 9 if i==3

  puts a 


}
#nil
#nil
#9
#nil

for i in 1..4

  a = 9 if i==3

  puts a

end
#nil
#nil
#9
#9

Trong vòng lặp 'for', biến cục bộ vẫn tồn tại sau mỗi vòng lặp. Trong vòng lặp 'mỗi', biến cục bộ làm mới sau mỗi vòng lặp.

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.