“ สำหรับ” กับ“ แต่ละ” ในทับทิม


200

ฉันเพิ่งมีคำถามอย่างรวดเร็วเกี่ยวกับลูปใน Ruby มีความแตกต่างระหว่างสองวิธีในการวนซ้ำผ่านคอลเลกชันหรือไม่?

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

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

เพียงแค่สงสัยว่าสิ่งเหล่านี้เหมือนกันหรืออาจจะมีความแตกต่างเล็กน้อย (อาจเป็นเมื่อ@collectionไม่มี)

คำตอบ:


315

นี่คือความแตกต่างเท่านั้น:

แต่ละ:

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

สำหรับ:

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

ด้วยforลูปตัวแปรตัววนซ้ำยังคงอยู่หลังจากบล็อกเสร็จสิ้น กับeachลูปมันจะไม่ยกเว้นว่ามันถูกกำหนดเป็นตัวแปรโลคัลก่อนที่ลูปจะเริ่มต้น

นอกเหนือจากนั้นforเป็นเพียงน้ำตาลไวยากรณ์สำหรับeachวิธีการ

เมื่อ@collectionเป็นnilลูปทั้งสองโยนข้อยกเว้น:

ข้อยกเว้น: ตัวแปรโลคัลหรือเมธอด `@collection 'ที่ไม่ได้กำหนดสำหรับ main: Object


3
มีเหตุผลที่ดีที่ทำไม x ยังอยู่ในกรณีหรือการออกแบบที่ไม่ดี: P? ดูเหมือนว่าฉันนี้ค่อนข้างใช้งานง่ายเปรียบเทียบกับภาษาอื่น ๆ ส่วนใหญ่
cyc115

3
@ cyc115 เหตุผลที่xยังคงอยู่ในสำหรับสถานการณ์เป็นเพราะความจริงที่ว่า (โดยทั่วไป) คำหลักที่ไม่ได้สร้างขอบเขตใหม่ ถ้า , เว้นแต่ , เริ่มต้น , สำหรับ , ในขณะที่ฯลฯ การทำงานทั้งหมดที่มีขอบเขตปัจจุบัน #eachอย่างไรก็ตามยอมรับบล็อก บล็อกจะเพิ่มขอบเขตของตนเองที่ด้านบนของขอบเขตปัจจุบันเสมอ ความหมายที่ประกาศตัวแปรใหม่ในบล็อก (ดังนั้นขอบเขตใหม่) จะไม่สามารถเข้าถึงได้จากนอกบล็อกเนื่องจากขอบเขตเพิ่มเติมนั้นไม่พร้อมใช้งาน
3limin4t0r

43

ดูที่ " The Evils of the For Loop " สำหรับคำอธิบายที่ดี (มีความแตกต่างเพียงเล็กน้อยเมื่อพิจารณาการกำหนดขอบเขตตัวแปร)

ใช้eachเป็นที่พิจารณาสำนวนเพิ่มเติมการใช้งานของทับทิม


@zachlatta: ขอบคุณสำหรับการแจ้งเตือน ฉันจะแก้ไขลิงก์ให้ชี้ไปที่ตัวแปร webarchive.org ของบทความ!
ChristopheD

1
graysoftinc.com/early-steps/the-evils-of-the-for-loopเป็นลิงค์ใหม่ตอนนี้เว็บไซต์ของ JEG2 กลับมาออนไลน์อีกครั้ง
pnomolos

30

ตัวอย่างแรกของคุณ

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

เป็นสำนวนมากขึ้น ในขณะที่ Ruby รองรับการสร้างลูปเช่นforและwhileโดยทั่วไปแล้วไวยากรณ์บล็อกจะเป็นที่ต้องการ

ความแตกต่างที่ลึกซึ้งอีกประการหนึ่งคือตัวแปรใด ๆ ที่คุณประกาศภายในforลูปจะมีอยู่ภายนอกลูปในขณะที่ตัวแปรภายในตัววนซ้ำนั้นจะเป็นส่วนตัวอย่างมีประสิทธิภาพ


whileและuntilมีการใช้งานที่เป็นรูปธรรมบางอย่างซึ่งไม่สามารถแทนที่ได้ด้วยเช่นการสร้างค่าที่ไม่ซ้ำกันหรือสำหรับ REPL
สูงสุด

6

อีกหนึ่งความแตกต่าง ..

number = ["one", "two", "three"]
 => ["one", "two", "three"] 

loop1 = []
loop2 = []

number.each do |c|
  loop1 << Proc.new { puts c }
end
 => ["one", "two", "three"] 

for c in number
  loop2 << Proc.new { puts c }
end
 => ["one", "two", "three"] 

loop1[1].call
two
 => nil 

loop2[1].call
three
 => nil 

แหล่งที่มา: http://paulphilippov.com/articles/enumerable-each-vs-for-loops-in-ruby

เพื่อความชัดเจนมากขึ้น: http://www.ruby-forum.com/topic/179264#784884


2

ดูเหมือนว่าไม่มีความแตกต่างforใช้eachอยู่ใต้

$ 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

อย่างที่ Bayard กล่าวว่าแต่ละคนมีสำนวนที่มากกว่า มันซ่อนเพิ่มเติมจากคุณและไม่ต้องการคุณสมบัติภาษาพิเศษ ต่อความคิดเห็นของ Telemachus

for .. in .. ตั้งตัววนซ้ำนอกขอบเขตของลูปดังนั้น

for a in [1,2]
  puts a
end

ใบไม้ที่aกำหนดหลังจากวนรอบเสร็จสิ้น eachไม่เหมือนที่ไหน ซึ่งเป็นอีกเหตุผลที่สนับสนุนการใช้eachงานเนื่องจากตัวแปร temp มีอายุการใช้งานสั้นลง


1
มีคือความแตกต่างเล็ก (ตาม yjerem, ChristopheD และเบยาร์ดกล่าวถึง) เกี่ยวกับขอบเขตตัวแปร
Telemachus

ไม่ถูกต้องforไม่ใช้eachภายใต้ ดูคำตอบอื่น ๆ
akuhn

@akuhn สำหรับการชี้แจงเพิ่มเติมโปรดดูคำถามนี้และทั้งสองคำตอบที่ยอดเยี่ยม
Sagar Pandya

2

ไม่เคยใช้งานforเลยอาจทำให้เกิดข้อบกพร่องที่ไม่สามารถแก้ไขได้

อย่าหลงกลนี่ไม่ใช่เรื่องของรหัสหรือลักษณะของสำนวน การดำเนินการของ Ruby forมีข้อบกพร่องร้ายแรงและไม่ควรใช้

นี่คือตัวอย่างที่forแนะนำข้อบกพร่อง

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

พิมพ์

quz
quz
quz

ใช้งาน%w{foo bar quz}.each { |n| ... }พิมพ์

foo
bar
quz

ทำไม?

ในforลูปตัวแปรnจะถูกกำหนดหนึ่งครั้งเท่านั้นและจากนั้นจะใช้คำจำกัดความเดียวสำหรับการวนซ้ำทั้งหมด ดังนั้นแต่ละบล็อคหมายถึงสิ่งเดียวกันnซึ่งมีค่าquzตามเวลาที่ลูปสิ้นสุด ข้อผิดพลาด!

ในeachลูปnจะมีการกำหนดตัวแปรใหม่สำหรับการวนซ้ำแต่ละครั้งตัวอย่างข้างบนตัวแปรnจะถูกกำหนดสามครั้งแยกกัน ดังนั้นแต่ละบล็อกหมายถึงการแยกnด้วยค่าที่ถูกต้อง


0

เท่าที่ฉันรู้การใช้บล็อกแทนโครงสร้างการควบคุมในภาษานั้นเป็นเรื่องที่สำนวนมากขึ้น


0

ฉันต้องการสร้างจุดเฉพาะเกี่ยวกับ for in loop ใน Ruby มันอาจดูเหมือนโครงสร้างคล้ายกับภาษาอื่น ๆ แต่ในความเป็นจริงมันเป็นนิพจน์เหมือนกับโครงสร้างวนลูปอื่น ๆ ทั้งหมดใน Ruby อันที่จริง, for in ทำงานกับอ็อบเจกต์ Enumerable เช่นเดียวกับ iterator แต่ละอัน

คอลเลกชันที่ส่งผ่านไปสำหรับในสามารถเป็นวัตถุใด ๆ ที่มีวิธีการวนซ้ำแต่ละ อาร์เรย์และแฮชจะกำหนดแต่ละวิธีและวัตถุอื่น ๆ ของทับทิมก็ทำเช่นกัน for / in loop เรียกใช้แต่ละเมธอดของวัตถุที่ระบุ เนื่องจากตัววนซ้ำนั้นให้ค่าสำหรับการวนรอบจะกำหนดค่าแต่ละค่า (หรือแต่ละชุดของค่า) ให้กับตัวแปรที่ระบุ (หรือตัวแปร) จากนั้นเรียกใช้รหัสในเนื้อความ

นี่เป็นตัวอย่างที่งี่เง่า แต่แสดงให้เห็นถึงจุดที่ว่า for in loop ทำงานกับวัตถุใด ๆ ที่มีแต่ละวิธีเช่นเดียวกับที่ตัววนซ้ำแต่ละตัวทำ:

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

และตอนนี้ตัววนซ้ำแต่ละตัว:

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

อย่างที่คุณเห็นทั้งสองตอบสนองต่อแต่ละวิธีที่ให้ค่ากลับไปที่บล็อก ดังที่ทุกคนกล่าวไว้ที่นี่มันดีกว่าที่จะใช้ตัววนซ้ำแบบวนซ้ำในการวนซ้ำ ฉันแค่อยากจะขับรถกลับบ้านในจุดที่ไม่มีอะไรวิเศษเกี่ยวกับในวง มันเป็นนิพจน์ที่เรียกใช้แต่ละวิธีของคอลเลกชันแล้วส่งผ่านไปยังบล็อกของโค้ด ดังนั้นจึงเป็นกรณีที่หายากมากที่คุณจะต้องใช้สำหรับในใช้ตัววนซ้ำแต่ละตัวเกือบตลอดเวลา (พร้อมด้วยประโยชน์เพิ่มเติมของขอบเขตบล็อก)


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

ใน 'for' loop ตัวแปรท้องถิ่นยังคงอยู่หลังจากวนรอบแต่ละครั้ง ในลูป 'แต่ละ' ตัวแปรโลคัลจะรีเฟรชหลังจากแต่ละลูป

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.