อะไรคือวิธีที่“ ถูกต้อง” ในการวนซ้ำในอาร์เรย์ใน Ruby


341

PHP สำหรับหูดทั้งหมดมันค่อนข้างดีในการนับนี้ ไม่มีความแตกต่างระหว่างอาเรย์และแฮช (บางทีฉันไร้เดียงสา แต่นี่ดูเหมือนจะถูกต้องสำหรับฉัน) และเพื่อย้ำผ่านคุณเพียงแค่ทำ

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

ใน Ruby มีหลายวิธีที่จะทำสิ่งนี้:

array.length.times do |i|
end

array.each

array.each_index

for i in array

Hashes มีเหตุผลมากกว่านี้เพราะฉันใช้เสมอ

hash.each do |key, value|

เหตุใดฉันจึงไม่สามารถทำสิ่งนี้กับอาร์เรย์ได้ ถ้าผมต้องการที่จะจำเพียงหนึ่งวิธีที่ผมคิดว่าผมสามารถใช้each_index(ตั้งแต่มันทำให้ทั้งดัชนีและความคุ้มค่าที่มีอยู่) แต่มันน่ารำคาญที่จะต้องทำแทนเพียงarray[index]value


โอ้ใช่ฉันลืมไปarray.each_with_indexแล้ว อย่างไรก็ตามอันนี้ครับเพราะมันไป|value, key|และhash.eachไป|key, value|! นี่มันไม่บ้าเหรอ?


1
ฉันเดาว่าarray#each_with_indexใช้|value, key|เพราะชื่อเมธอดมีความหมายของคำสั่งในขณะที่คำสั่งที่ใช้ในการhash#eachเลียนแบบhash[key] = valueไวยากรณ์?
Benjineer

3
หากคุณเพิ่งเริ่มต้นใช้งานลูปใน Ruby จากนั้นลองใช้การเลือกปฏิเสธการรวบรวมการฉีดและการตรวจจับ
Matthew Carriere

คำตอบ:


560

สิ่งนี้จะวนซ้ำองค์ประกอบทั้งหมด:

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

พิมพ์:

1
2
3
4
5
6

สิ่งนี้จะวนซ้ำองค์ประกอบทั้งหมดที่ให้คุณค่าและดัชนีแก่คุณ:

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

พิมพ์:

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

ฉันไม่แน่ใจจากคำถามของคุณที่คุณกำลังมองหา


1
นอกหัวข้อฉันไม่สามารถหา each_with_index ใน ruby ​​1.9 ในอาร์เรย์
CantGetANick

19
@CantGetANick: คลาส Array มีโมดูล Enumerable ซึ่งมีวิธีนี้
xuinkrbin

88

ผมคิดว่าไม่มีใครที่เหมาะสมทาง มีหลายวิธีในการย้ำและแต่ละคนมีช่องของตัวเอง

  • each เพียงพอสำหรับประเพณีหลายอย่างเนื่องจากฉันไม่ค่อยสนใจดัชนี
  • each_ with _index ทำหน้าที่เหมือนแฮช # แต่ละรายการคุณจะได้รับคุณค่าและดัชนี
  • each_index- เพียงดัชนี ฉันไม่ได้ใช้อันนี้บ่อยๆ เทียบเท่ากับ "length.times"
  • map เป็นอีกวิธีหนึ่งในการทำซ้ำซึ่งมีประโยชน์เมื่อคุณต้องการแปลงอาร์เรย์หนึ่งเป็นอีกอาร์เรย์หนึ่ง
  • select เป็นตัววนซ้ำที่จะใช้เมื่อคุณต้องการเลือกชุดย่อย
  • inject มีประโยชน์สำหรับการสร้างผลรวมหรือผลิตภัณฑ์หรือรวบรวมผลลัพธ์เดียว

มันอาจดูเหมือนเป็นเรื่องที่ต้องจดจำ แต่ไม่ต้องกังวลคุณสามารถทำได้โดยไม่ต้องรู้ทั้งหมด แต่เมื่อคุณเริ่มเรียนรู้และใช้วิธีการต่าง ๆ รหัสของคุณจะสะอาดและชัดเจนยิ่งขึ้นและคุณจะไปถึง Ruby mastery


คำตอบที่ดี! ฉันต้องการพูดถึงการย้อนกลับเช่น #reject และ aliases เช่น #collect
Emily Tui Ch

60

ฉันไม่ได้บอกว่าArray-> |value,index|และHash-> |key,value|ไม่ได้บ้า (ดูความคิดเห็นของ Horace Loeb) แต่ฉันกำลังพูดว่ามีวิธีที่มีเหตุผลที่จะคาดหวังข้อตกลงนี้

เมื่อฉันจัดการกับอาร์เรย์ฉันมุ่งเน้นไปที่องค์ประกอบในอาร์เรย์ (ไม่ใช่ดัชนีเพราะดัชนีนั้นชั่วคราว) ใช้วิธีการแต่ละคนมีดัชนีเช่นดัชนี + แต่ละหรือ | แต่ละดัชนี | |value,index|หรือ สิ่งนี้สอดคล้องกับดัชนีที่ถูกมองว่าเป็นอาร์กิวเมนต์ที่เป็นทางเลือกเช่น | value | เทียบเท่ากับ | value, index = nil | ซึ่งสอดคล้องกับ | ค่าดัชนี |

เมื่อฉันจัดการกับ hashes ผมมากขึ้นมักจะมุ่งเน้นไปที่ปุ่มกว่าค่าและฉันกำลังมักจะจัดการกับคีย์และค่าในลำดับที่ทั้งหรือkey => valuehash[key] = value

หากคุณต้องการการพิมพ์แบบเป็ดให้ใช้วิธีการที่กำหนดไว้อย่างชัดเจนเช่น Brent Longborough ที่แสดงหรือวิธีการทางอ้อมตามที่ maxhawkins แสดง

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

สำหรับคำถามดั้งเดิม "อะไรคือ" วิธีที่ถูกต้อง "ในการวนซ้ำผ่านอาร์เรย์ใน Ruby?" ดีฉันคิดว่าวิธีที่สำคัญ (เช่นไม่มีพลัง syntactic น้ำตาลหรืออำนาจเชิงวัตถุ) คือ:

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

แต่รูบีนั้นเกี่ยวกับน้ำตาล syntactic ที่ทรงพลังและพลังเชิงวัตถุ แต่อย่างไรก็ตามนี่คือสิ่งที่เทียบเท่ากับแฮชและสามารถสั่งซื้อกุญแจได้หรือไม่:

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

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


ตอนนี้ฉันต้องการถามคำถามอื่นว่า "อะไรคือ" วิธีที่ถูก "ในการทำซ้ำใน Range ใน Ruby backward?"! (คำถามนี้เป็นวิธีที่ฉันมาที่หน้านี้)

มันเป็นการดีที่จะทำ (สำหรับการส่งต่อ):

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

แต่ฉันไม่ชอบทำ (ย้อนหลัง):

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

จริง ๆ แล้วฉันไม่ได้สนใจที่จะทำสิ่งนั้นมากเกินไป แต่เมื่อฉันสอนไปข้างหลังฉันต้องการแสดงให้นักเรียนเห็นว่ามีความสมมาตรที่ดี (เช่นมีความแตกต่างน้อยที่สุดเช่นเพียงเพิ่มสิ่งที่ตรงกันข้ามหรือขั้นตอน -1 แต่ไม่มี แก้ไขสิ่งอื่นใด) คุณสามารถทำได้ (เพื่อความสมมาตร):

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

และ

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

ซึ่งฉันไม่ชอบมาก แต่คุณไม่สามารถทำ

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

ในที่สุดคุณสามารถทำได้

class Range

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

end

แต่ฉันต้องการสอนทับทิมบริสุทธิ์แทนที่จะเป็นวิธีการเชิงวัตถุ ฉันต้องการย้ำถอยหลัง:

  • โดยไม่ต้องสร้างอาร์เรย์ (พิจารณา 0..1000000000)
  • ทำงานได้กับทุก Range (เช่น Strings ไม่ใช่แค่จำนวนเต็ม)
  • โดยไม่ใช้พลังเชิงวัตถุพิเศษใด ๆ (เช่นไม่มีการดัดแปลงคลาส)

ฉันเชื่อว่าสิ่งนี้เป็นไปไม่ได้หากไม่มีการกำหนดpredวิธีการซึ่งหมายถึงการแก้ไขคลาส Range เพื่อใช้งาน หากคุณสามารถทำได้โปรดแจ้งให้เราทราบมิฉะนั้นการยืนยันความเป็นไปไม่ได้จะได้รับการชื่นชมแม้ว่ามันจะน่าผิดหวัง บางที Ruby 1.9 จะพูดถึงเรื่องนี้

(ขอบคุณสำหรับเวลาที่อ่านสิ่งนี้)


5
1.upto(10) do |i| puts i endและ10.downto(1) do puts i endประเภทของความสมมาตรที่คุณต้องการ หวังว่าจะช่วย ไม่แน่ใจว่ามันจะใช้งานได้กับสตริงหรือไม่
Suren

(1..10) .to_a.sort {| x, y | y <=> x} .each {| i | ทำให้ "i = # {i}"} - กลับด้านช้ากว่า
Davidslv

[*1..10].each{|i| puts "i=#{i}" }คุณสามารถ
Hauleth


12

คำตอบอื่น ๆ นั้นใช้ได้ แต่ฉันต้องการที่จะชี้ให้เห็นสิ่งหนึ่งต่อพ่วงอื่น ๆ : อาร์เรย์ได้รับคำสั่งในขณะที่ Hashes ไม่ได้อยู่ใน 1.8 (ใน Ruby 1.9, HASH ได้รับคำสั่งโดยการใส่คำสั่งของคีย์) ดังนั้นมันจะไม่สมเหตุสมผลก่อนที่ 1.9 จะทำซ้ำผ่าน Hash ในแบบ / ลำดับเดียวกับ Arrays ซึ่งมีการเรียงลำดับที่แน่นอนเสมอ ฉันไม่ทราบว่าคำสั่งเริ่มต้นสำหรับอาร์เรย์ที่เชื่อมโยงกับ PHP คืออะไร (เห็นได้ชัดว่า google fu ของฉันไม่แข็งแรงพอที่จะหาได้) แต่ฉันไม่รู้ว่าคุณสามารถพิจารณาอาร์เรย์ PHP ปกติและ PHP ที่เชื่อมโยงกับอาร์เรย์ได้อย่างไร เป็น "เหมือนกัน" ในบริบทนี้เนื่องจากลำดับสำหรับการเชื่อมโยงแบบเรียงลำดับดูเหมือนไม่ได้กำหนดไว้

ดังนั้นวิธีการของฉันจึงดูชัดเจนและใช้งานง่ายกว่าสำหรับฉัน :)


1
แฮชและอาร์เรย์เป็นสิ่งเดียวกัน! อาร์เรย์จำนวนเต็มแผนที่วัตถุและแฮชวัตถุแผนที่วัตถุ อาร์เรย์เป็นเพียงกรณีพิเศษของการแฮชใช่ไหม
Tom Lehman

1
อย่างที่ฉันบอกไปอาเรย์เป็นชุดสั่ง การแมป (ในความหมายทั่วไป) ไม่มีการจัดลำดับ หากคุณ จำกัด ชุดคีย์เป็นจำนวนเต็ม (เช่นกับอาร์เรย์) มันจะเกิดขึ้นเมื่อชุดคีย์มีคำสั่งซื้อ ในการแมปทั่วไป (Hash / Associative Array) คีย์อาจไม่มีใบสั่ง
Pistos

@Horace: แฮชและอาร์เรย์ไม่เหมือนกัน หากหนึ่งเป็นกรณีพิเศษของอีกพวกเขาจะไม่เหมือนกัน แต่ยิ่งแย่ไปกว่านั้นอาร์เรย์ไม่ใช่แฮชชนิดพิเศษนั่นเป็นเพียงวิธีที่เป็นนามธรรมในการรับชม ดังที่เบรนท์ชี้ให้เห็นข้างต้นการใช้แฮชและอาร์เรย์สลับกันอาจบ่งบอกถึงกลิ่นรหัส
Zane

9

การพยายามทำสิ่งเดียวกันอย่างสม่ำเสมอด้วยอาร์เรย์และแฮชอาจเป็นเพียงกลิ่นรหัส แต่ความเสี่ยงที่ฉันถูกตราหน้าว่าเป็นลิงครึ่งลิงตัวปะหากคุณกำลังมองหาพฤติกรรมที่สอดคล้องกันนี่จะเป็นการหลอกลวง ?:

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

ต่อไปนี้เป็นสี่ตัวเลือกที่ระบุไว้ในคำถามของคุณซึ่งจัดเรียงโดยอิสระในการควบคุม คุณอาจต้องการใช้อันที่แตกต่างกันขึ้นอยู่กับสิ่งที่คุณต้องการ

  1. เพียงแค่ผ่านค่า:

    array.each
  2. เพียงแค่อ่านดัชนี:

    array.each_index
  3. ผ่านดัชนี + ตัวแปรดัชนี:

    for i in array
  4. การควบคุมวงนับ + ตัวแปรดัชนี:

    array.length.times do | i |

5

ฉันพยายามสร้างเมนู (ในCampingและMarkaby ) โดยใช้แฮช

แต่ละรายการมี 2 องค์ประกอบ: ป้ายเมนูและURLดังนั้นแฮชที่ถูกต้อง แต่ '/' URL สำหรับ 'หน้าแรก' จะปรากฏเป็นรายการสุดท้ายเสมอ ใบสั่ง.

การใช้อาเรย์ด้วยeach_sliceการทำงาน:

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

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

ดังนั้นนี่เป็นเพียงการกล่าวขอบคุณสำหรับคำใบ้โดยไม่ได้ตั้งใจในการแก้ปัญหา!

ชัดเจน แต่ควรระบุ: ฉันแนะนำให้ตรวจสอบว่าความยาวของอาร์เรย์หารด้วยค่าส่วนแบ่งหรือไม่


4

ถ้าคุณใช้การนับ mixin (ตามทางรถไฟไม่) คุณสามารถทำสิ่งที่คล้ายกับ PHP ข้อมูลที่ระบุไว้ เพียงใช้เมธอด each_slice และทำการแฮชให้แบน

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}" }

ต้องการการปะติดลิงน้อย

อย่างไรก็ตามสิ่งนี้จะทำให้เกิดปัญหาเมื่อคุณมีอาร์เรย์แบบเรียกซ้ำหรือแฮชที่มีค่าอาร์เรย์ ในทับทิม 1.9 ปัญหานี้แก้ไขได้ด้วยพารามิเตอร์ของวิธีการแบนที่ระบุว่าจะลึกลงไปได้อย่างไร

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

สำหรับคำถามที่ว่านี่เป็นกลิ่นรหัสฉันไม่แน่ใจ โดยปกติเมื่อฉันต้องโค้งงอไปด้านหลังเพื่อทำซ้ำสิ่งที่ฉันก้าวถอยหลังและตระหนักว่าฉันกำลังโจมตีปัญหาผิด


2

วิธีที่ถูกต้องคือสิ่งที่คุณรู้สึกสะดวกสบายที่สุดและทำในสิ่งที่คุณต้องการทำ ในการเขียนโปรแกรมมักไม่ค่อยมีวิธีทำสิ่งที่ถูกต้องบ่อยครั้งมีหลายวิธีให้เลือก

หากคุณพอใจกับวิธีการทำสิ่งต่าง ๆ ให้ทำมันเว้นแต่มันจะไม่ทำงาน - ถึงเวลาที่จะหาวิธีที่ดีกว่า


2

ใน Ruby 2.1 เมธอด each_with_index จะถูกลบออก แต่คุณสามารถใช้each_index ได้

ตัวอย่าง:

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

ผลิต:

0 -- 1 -- 2 --

4
นี่ไม่เป็นความจริง. ตามที่ระบุไว้ในความคิดเห็นของคำตอบที่ยอมรับเหตุผลที่each_with_indexไม่ปรากฏในเอกสารเป็นเพราะมันมีให้โดยEnumerableโมดูล
ihaztehcodez

0

การใช้วิธีการเดียวกันสำหรับการวนซ้ำทั้งอาร์เรย์และแฮชทำให้เข้าใจได้ตัวอย่างเช่นการประมวลผลโครงสร้างแฮชและอาเรย์ที่ซ้อนกันมักเกิดจากตัวแยกวิเคราะห์จากการอ่านไฟล์ JSON เป็นต้น

วิธีหนึ่งที่ชาญฉลาดที่ยังไม่ได้กล่าวถึงคือการทำในไลบรารีRuby Facetsของส่วนขยายไลบรารีมาตรฐาน จากที่นี่ :

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

มีอยู่แล้วนามแฝงของHash#each_pair Hash#eachดังนั้นหลังจากแพทช์นี้เรายังมีArray#each_pairและสามารถใช้สลับกันเพื่อทำซ้ำผ่านทั้งแฮชและอาร์เรย์ การแก้ไขนี้ OP ของความบ้าตั้งข้อสังเกตว่ามีการขัดแย้งบล็อกกลับเมื่อเทียบกับArray#each_with_index Hash#eachตัวอย่างการใช้งาน:

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