มีเหตุผลไหมที่เราไม่สามารถทำซ้ำใน "Reverse Range" ในทับทิมได้?


104

ฉันพยายามทำซ้ำไปข้างหลังโดยใช้ Range และeach:

(4..0).each do |i|
  puts i
end
==> 4..0

การวนซ้ำ0..4เขียนตัวเลข ในอีกช่วงหนึ่งr = 4..0ดูเหมือนจะโอเคr.first == 4, r.last == 0.

ดูเหมือนว่าจะแปลกสำหรับฉันที่โครงสร้างข้างต้นไม่ได้ผลลัพธ์ที่คาดหวัง อะไรคือเหตุผลสำหรับสิ่งนั้น? อะไรคือสถานการณ์เมื่อพฤติกรรมนี้สมเหตุสมผล?


ฉันไม่เพียง แต่สนใจที่จะตระหนักถึงการทำซ้ำนี้ซึ่งเห็นได้ชัดว่าไม่รองรับ แต่ทำไมมันถึงส่งคืนช่วง 4..0 เอง อะไรคือความตั้งใจของนักออกแบบภาษา? เหตุใดจึงเป็นเรื่องดี ฉันเห็นพฤติกรรมที่คล้ายกันในโครงสร้างทับทิมอื่น ๆ เช่นกันและมันก็ยังไม่สะอาดเมื่อมันมีประโยชน์
fifigyuri

1
ช่วงนั้นจะถูกส่งกลับโดยการประชุม เนื่องจาก.eachคำสั่งไม่ได้แก้ไขอะไรเลยจึงไม่มี "ผลลัพธ์" ที่คำนวณแล้วให้ส่งกลับ ในกรณีนี้ Ruby มักจะส่งคืนออบเจ็กต์ดั้งเดิมเมื่อสำเร็จและเกิดnilข้อผิดพลาด ซึ่งช่วยให้คุณใช้นิพจน์เช่นนี้เป็นเงื่อนไขในifคำสั่ง
bta

คำตอบ:


100

ช่วงคือสิ่งที่กำหนดโดยจุดเริ่มต้นและจุดสิ้นสุดไม่ใช่โดยเนื้อหา การ "วนซ้ำ" ในช่วงไม่สมเหตุสมผลในกรณีทั่วไป ตัวอย่างเช่นลองพิจารณาว่าคุณจะ "วนซ้ำ" อย่างไรในช่วงที่เกิดจากวันที่สองวัน คุณจะทำซ้ำในแต่ละวันหรือไม่? ตามเดือน? ตามปี? ตามสัปดาห์? มันไม่ได้กำหนดไว้อย่างชัดเจน IMO ความจริงที่อนุญาตสำหรับช่วงการส่งต่อควรถูกมองว่าเป็นวิธีอำนวยความสะดวกเท่านั้น

หากคุณต้องการย้อนกลับในช่วงดังกล่าวคุณสามารถใช้downto:

$ r = 10..6
=> 10..6

$ (r.first).downto(r.last).each { |i| puts i }
10
9
8
7
6

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


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

3
ฉันยังคงคิดว่าการกำหนดการทำซ้ำแบบ "ใช้งานง่าย" สำหรับหลายช่วงเป็นเรื่องยากที่จะทำอย่างต่อเนื่องและฉันไม่เห็นด้วยว่าการวนซ้ำในวันที่แบบนั้นโดยสังหรณ์ใจว่าขั้นตอนเท่ากับ 1 วันหลังจากนั้นวันหนึ่ง ๆ ก็เป็นช่วงของ เวลา (ตั้งแต่เที่ยงคืนถึงเที่ยงคืน) ตัวอย่างเช่นใครจะบอกว่า "1 มกราคมถึง 18 สิงหาคม" (ตรงกับ 20 สัปดาห์) ไม่ได้หมายความถึงการวนซ้ำของสัปดาห์แทนที่จะเป็นวัน ทำไมไม่ทำซ้ำทีละชั่วโมงนาทีหรือวินาที
John Feminella

8
.eachมีซ้ำซ้อน5.downto(1) { |n| puts n }ทำงานได้ดี นอกจากนี้แทนทุกสิ่งที่ r.last r.first (6..10).reverse_eachเพียงแค่ทำ
mk12

@ Mk12: เห็นด้วย 100% ฉันแค่พยายามทำตัวให้ชัดเจนที่สุดเพื่อประโยชน์ของ Rubyists รุ่นใหม่ บางทีมันอาจจะสับสนเกินไป
John Feminella

เมื่อพยายามเพิ่มปีในแบบฟอร์มฉันใช้:= f.select :model_year, (Time.zone.now.year + 1).downto(Time.zone.now.year - 100).to_a
Eric Norcross

92

แล้ว(0..1).reverse_eachจะวนซ้ำช่วงใดไปข้างหลัง?


1
สร้างอาร์เรย์ชั่วคราวอย่างน้อยใน 2.4.1 ruby-doc.org/core-2.4.1/Enumerable.html#method-i-reverse_each
kolen

1
สิ่งนี้ใช้ได้ผลสำหรับฉันในการจัดเรียงช่วงวันที่(Date.today.beginning_of_year..Date.today.yesterday).reverse_eachขอบคุณ
daniel

18

การทำซ้ำในช่วงใน Ruby พร้อมกับeachเรียกใช้succเมธอดบนวัตถุแรกในช่วง

$ 4.succ
=> 5

และ 5 อยู่นอกช่วง

คุณสามารถจำลองการย้อนกลับได้ด้วยแฮ็คนี้:

(-4..0).each { |n| puts n.abs }

จอห์นชี้ให้เห็นว่าสิ่งนี้จะใช้ไม่ได้หากมีช่วง 0 สิ่งนี้จะ:

>> (-2..2).each { |n| puts -n }
2
1
0
-1
-2
=> -2..2

ไม่สามารถพูดได้ว่าฉันชอบพวกเขามาก ๆ เพราะมันบดบังเจตนา


2
ไม่ได้ แต่การคูณด้วย -1 แทนที่จะใช้ .abs คุณทำได้
Jonas Elfström

12

อ้างอิงจากหนังสือ "Programming Ruby" วัตถุ Range จะเก็บจุดปลายทั้งสองของช่วงและใช้.succสมาชิกเพื่อสร้างค่ากลาง ขึ้นอยู่กับประเภทข้อมูลที่คุณใช้ในช่วงของคุณคุณสามารถสร้างคลาสย่อยIntegerและกำหนด.succสมาชิกใหม่ได้ตลอดเวลาเพื่อให้ทำหน้าที่เหมือนตัวย้อนกลับ (คุณอาจต้องการกำหนดใหม่ด้วย.nextเช่นกัน)

คุณยังสามารถบรรลุผลลัพธ์ที่ต้องการได้โดยไม่ต้องใช้ Range ลองสิ่งนี้:

4.step(0, -1) do |i|
    puts i
end

ขั้นตอนนี้จะเพิ่มจาก 4 ถึง 0 ในขั้นตอนที่ -1 อย่างไรก็ตามฉันไม่รู้ว่าสิ่งนี้จะใช้ได้กับอะไรยกเว้นอาร์กิวเมนต์จำนวนเต็ม




4

ถ้ารายการไม่ใหญ่ขนาดนั้น ฉันคิดว่า [*0..4].reverse.each { |i| puts i } เป็นวิธีที่ง่ายที่สุด


2
IMO โดยทั่วไปถือว่าเป็นเรื่องที่ดี ฉันคิดว่ามันเป็นความเชื่อและนิสัยที่ถูกต้องที่จะปฏิบัติตามโดยทั่วไป และในขณะที่ปีศาจไม่เคยหลับใหลฉันไม่ไว้ใจตัวเองว่าฉันจำได้ว่าฉันวนซ้ำอยู่ที่ไหน แต่คุณพูดถูกถ้าเรามีค่าคงที่ 0 และ 4 การวนซ้ำอาร์เรย์อาจไม่ทำให้เกิดปัญหาใด ๆ
fifigyuri

1

ดังที่ bta กล่าวไว้เหตุผลก็คือRange#eachส่งsuccไปยังจุดเริ่มต้นจากนั้นไปยังผลลัพธ์ของการsuccเรียกนั้นและอื่น ๆ จนกว่าผลลัพธ์จะมากกว่าค่าสุดท้าย คุณไม่สามารถโทรจาก 4 ถึง 0 succได้และในความเป็นจริงคุณได้เริ่มต้นมากกว่าจุดจบแล้ว


1

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

class Range

  def each(&block)
    direction = (first<=last ? 1 : -1)
    i = first
    not_reached_the_end = if first<=last
                            lambda {|i| i<=last}
                          else
                            lambda {|i| i>=last}
                          end
    while not_reached_the_end.call(i)
      yield i
      i += direction
    end
  end
end

0

สิ่งนี้ใช้ได้กับกรณีการใช้งานที่ขี้เกียจของฉัน

(-999999..0).lazy.map{|x| -x}.first(3)
#=> [999999, 999998, 999997]

0

OP เขียน

ดูเหมือนว่าจะแปลกสำหรับฉันที่โครงสร้างข้างต้นไม่ได้ผลลัพธ์ที่คาดหวัง อะไรคือเหตุผลสำหรับสิ่งนั้น? อะไรคือสถานการณ์เมื่อพฤติกรรมนี้สมเหตุสมผล?

ไม่สามารถทำได้หรือไม่ แต่เพื่อตอบคำถามที่ไม่ได้ถามก่อนที่จะไปถึงคำถามที่ถามจริง:

$ irb
2.1.5 :001 > (0..4)
 => 0..4
2.1.5 :002 > (0..4).each { |i| puts i }
0
1
2
3
4
 => 0..4
2.1.5 :003 > (4..0).each { |i| puts i }
 => 4..0
2.1.5 :007 > (0..4).reverse_each { |i| puts i }
4
3
2
1
0
 => 0..4
2.1.5 :009 > 4.downto(0).each { |i| puts i }
4
3
2
1
0
 => 4

เนื่องจาก reverse_each ถูกอ้างว่าสร้างอาร์เรย์ทั้งหมดการหยุดทำงานจะมีประสิทธิภาพมากขึ้นอย่างชัดเจน ความจริงที่ว่านักออกแบบภาษาสามารถพิจารณานำสิ่งต่างๆเช่นนั้นไปใช้กับคำตอบของคำถามจริงตามที่ถามได้

เพื่อตอบคำถามตามที่ถามจริง ...

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

nil.to_s
   .to_s
   .inspect

ผลลัพธ์เป็น "" แต่

nil.to_s
#  .to_s   # Don't want this one for now
   .inspect

ผลลัพธ์ใน

 syntax error, unexpected '.', expecting end-of-input
 .inspect
 ^

คุณอาจคาดหวัง << และผลักดันให้เหมือนกันสำหรับการต่อท้ายอาร์เรย์ แต่

a = []
a << *[:A, :B]    # is illegal but
a.push *[:A, :B]  # isn't.

คุณอาจคาดหวังว่า 'grep' จะทำงานเหมือนกับบรรทัดคำสั่ง Unix ที่เทียบเท่า แต่มันไม่ === จับคู่ไม่ = ~ แม้จะมีชื่อก็ตาม

$ echo foo | grep .
foo
$ ruby -le 'p ["foo"].grep(".")'
[]

วิธีการต่างๆเป็นนามแฝงซึ่งกันและกันโดยไม่คาดคิดดังนั้นคุณต้องเรียนรู้ชื่อหลายชื่อสำหรับสิ่งเดียวกันเช่นfindและdetectแม้ว่าคุณจะชอบนักพัฒนาซอฟต์แวร์ส่วนใหญ่และเคยใช้เพียงชื่อเดียว มากเหมือนกันไปสำหรับsize, countและlengthยกเว้นสำหรับการเรียนที่กำหนดแต่ละที่แตกต่างกันหรือไม่ได้กำหนดหนึ่งหรือสองที่ทั้งหมด

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

วัตถุตัวแปรสภาพแวดล้อม ENV ไม่รองรับ 'ผสาน' ดังนั้นคุณต้องเขียน

 ENV.to_h.merge('a': '1')

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


สิ่งนี้ไม่ได้ตอบคำถามในลักษณะรูปร่างหรือรูปแบบใด ๆ ไม่มีอะไรนอกจากคุยโวเกี่ยวกับสิ่งที่ผู้เขียนไม่ชอบเกี่ยวกับ Ruby
Jörg W Mittag

อัปเดตเพื่อตอบคำถามที่ไม่ได้ถามนอกเหนือจากคำตอบที่ถามจริง พูดจาโผงผาง: คำกริยา 1. พูดหรือตะโกนด้วยความโกรธ คำตอบเดิมไม่ได้โกรธหรือไม่พอใจมันเป็นการตอบสนองที่พิจารณาพร้อมตัวอย่าง
android.weasel

@ JörgWMittagคำถามเดิมยังรวมถึง: ดูเหมือนว่าจะแปลกสำหรับฉันที่โครงสร้างข้างต้นไม่ให้ผลลัพธ์ที่คาดหวัง อะไรคือเหตุผลสำหรับสิ่งนั้น? อะไรคือสถานการณ์เมื่อพฤติกรรมนี้สมเหตุสมผล? ดังนั้นเขาจึงมีเหตุผลไม่ใช่แก้ปัญหาโค้ด
android.weasel

อีกครั้งฉันไม่เห็นว่าลักษณะการทำงานgrepเป็นอย่างไรรูปร่างหรือรูปแบบใด ๆ ที่เกี่ยวข้องกับความจริงที่ว่าการทำซ้ำในช่วงว่างเปล่านั้นเป็นการไม่ดำเนินการ ฉันไม่เห็นว่าความจริงที่ว่าการทำซ้ำในช่วงที่ว่างเปล่านั้นเป็น no-op นั้นมีรูปร่างหรือรูปแบบที่ "น่าประหลาดใจไม่รู้จบ" และ "เสียอย่างสิ้นเชิง" อย่างไร
Jörg W Mittag

เนื่องจากช่วง 4..0 มีเจตนาที่ชัดเจนคือ [4, 3, 2, 1, 0] แต่น่าแปลกที่ไม่ได้เตือนด้วยซ้ำ มันทำให้ OP แปลกใจและทำให้ฉันประหลาดใจและทำให้คนอื่น ๆ หลายคนประหลาดใจอย่างไม่ต้องสงสัย ฉันแสดงตัวอย่างอื่น ๆ ของพฤติกรรมที่น่าแปลกใจ ฉันสามารถอ้างอิงเพิ่มเติมได้ถ้าคุณต้องการ เมื่อมีบางสิ่งบางอย่างแสดงพฤติกรรมที่น่าประหลาดใจมากกว่าจำนวนหนึ่งมันก็เริ่มลอยเข้ามาในอาณาเขตของ 'แตก คล้ายกับการที่ค่าคงที่เตือนเมื่อเขียนทับโดยเฉพาะอย่างยิ่งเมื่อวิธีการไม่ทำ
android.weasel

0

สำหรับฉันวิธีที่ง่ายที่สุดคือ:

[*0..9].reverse

อีกวิธีในการทำซ้ำสำหรับการแจงนับ:

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