Có lý do gì mà chúng ta không thể lặp lại trên "Dải ngược" trong ruby ​​không?


104

Tôi đã cố gắng lặp lại bằng cách sử dụng Phạm vi và each:

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

Lặp lại thông qua 0..4ghi các số. Mặt khác Phạm vi r = 4..0có vẻ là ok, r.first == 4, r.last == 0.

Có vẻ như là lạ đối với tôi rằng cấu trúc trên không tạo ra kết quả như mong đợi. Lý do cho điều đó là gì? Những tình huống nào khi hành vi này là hợp lý?


Tôi không chỉ quan tâm đến việc làm thế nào để nhận ra sự lặp lại này, điều này rõ ràng là không được hỗ trợ, mà còn là tại sao nó tự trả về phạm vi 4..0. Ý định của các nhà thiết kế ngôn ngữ là gì? Tại sao, nó tốt trong những tình huống nào? Tôi cũng thấy hành vi tương tự trong các cấu trúc ruby ​​khác, và nó vẫn không sạch khi nó hữu ích.
Fifigyuri

1
Phạm vi chính nó được trả về theo quy ước. Vì .eachcâu lệnh không sửa đổi bất cứ điều gì, không có "kết quả" được tính toán nào để trả về. Trong trường hợp này, Ruby thường trả về đối tượng ban đầu khi thành công và nilkhi bị lỗi. Điều này cho phép bạn sử dụng các biểu thức như thế này làm điều kiện trên một ifcâu lệnh.
bta

Câu trả lời:


99

Một phạm vi chỉ là: một cái gì đó được xác định bởi bắt đầu và kết thúc của nó, không phải bởi nội dung của nó. "Lặp lại" trên một phạm vi không thực sự có ý nghĩa trong trường hợp chung. Ví dụ: hãy xem xét cách bạn sẽ "lặp lại" trong phạm vi được tạo bởi hai ngày. Bạn có lặp lại theo ngày không? theo tháng? Theo năm? Theo tuần? Nó không được xác định rõ ràng. IMO, thực tế là nó được phép cho phạm vi chuyển tiếp chỉ nên được xem như một phương pháp tiện lợi.

Nếu bạn muốn lặp lại trong một phạm vi như vậy, bạn luôn có thể sử dụng downto:

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

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

Dưới đây là một số suy nghĩ khác của những người khác về lý do tại sao rất khó để vừa cho phép lặp lại vừa phải đối phó một cách nhất quán với phạm vi đảo ngược.


10
Tôi nghĩ rằng việc lặp lại trong phạm vi từ 1 đến 100 hoặc từ 100 đến 1 có nghĩa là sử dụng bước 1. Nếu ai đó muốn một bước khác, hãy thay đổi giá trị mặc định. Tương tự, đối với tôi (ít nhất) lặp lại từ ngày 1 tháng 1 đến ngày 16 tháng 8 có nghĩa là từng ngày. Tôi nghĩ rằng thường có điều gì đó mà chúng ta có thể đồng ý với nhau, bởi vì chúng ta có ý nghĩa trực giác như vậy. Cảm ơn câu trả lời của bạn, liên kết bạn đưa ra cũng hữu ích.
XVigyuri,

3
Tôi vẫn nghĩ rằng việc xác định số lần lặp lại "trực quan" cho nhiều phạm vi là một thách thức để thực hiện nhất quán và tôi không đồng ý rằng việc lặp qua các ngày theo cách trực quan ngụ ý một bước tương đương với 1 ngày - xét cho cùng, bản thân một ngày đã là một phạm vi thời gian (từ nửa đêm đến nửa đêm). Ví dụ: ai có thể nói rằng "từ ngày 1 tháng 1 đến ngày 18 tháng 8" (chính xác là 20 tuần) không ngụ ý sự lặp lại của tuần thay vì ngày? Tại sao không lặp lại theo giờ, phút hoặc giây?
John Feminella

8
.eachthừa, 5.downto(1) { |n| puts n }hoạt động tốt. Ngoài ra, thay vì tất cả những thứ cuối cùng r. Đầu tiên đó, chỉ cần làm (6..10).reverse_each.
mk

@ Mk12: 100% đồng ý, tôi chỉ đang cố tỏ ra siêu rõ ràng vì lợi ích của những người theo chủ nghĩa Ruby mới hơn. Có lẽ điều đó quá khó hiểu.
John Feminella

Khi cố gắng thêm năm vào một biểu mẫu, tôi đã sử dụng:= f.select :model_year, (Time.zone.now.year + 1).downto(Time.zone.now.year - 100).to_a
Eric Norcross


18

Lặp lại trên một phạm vi trong Ruby với eachcác lệnh gọi succphương thức trên đối tượng đầu tiên trong phạm vi.

$ 4.succ
=> 5

Và 5 nằm ngoài phạm vi.

Bạn có thể mô phỏng lặp lại ngược với hack này:

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

John chỉ ra rằng điều này sẽ không hoạt động nếu nó kéo dài đến 0. Điều này sẽ:

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

Không thể nói rằng tôi thực sự thích bất kỳ ai trong số họ bởi vì họ loại bỏ ý định.


2
Không, nhưng bằng cách nhân với -1 thay vì sử dụng .abs bạn có thể.
Jonas Elfström

12

Theo cuốn sách "Lập trình Ruby", đối tượng Range lưu trữ hai điểm cuối của phạm vi và sử dụng .succthành viên để tạo ra các giá trị trung gian. Tùy thuộc vào loại dữ liệu bạn đang sử dụng trong phạm vi của mình, bạn luôn có thể tạo một lớp con Integervà xác định lại .succthành viên để nó hoạt động giống như một trình lặp ngược (có thể bạn cũng muốn định nghĩa lại .next).

Bạn cũng có thể đạt được kết quả bạn đang tìm kiếm mà không cần sử dụng Phạm vi. Thử cái này:

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

Điều này sẽ chuyển từ 4 đến 0 trong các bước -1. Tuy nhiên, tôi không biết liệu điều này có hoạt động với bất kỳ điều gì ngoại trừ các đối số Số nguyên hay không.



5

Bạn thậm chí có thể sử dụng một forvòng lặp:

for n in 4.downto(0) do
  print n
end

mà in:

4
3
2
1
0

3

nếu danh sách không phải là lớn. tôi nghĩ [*0..4].reverse.each { |i| puts i } là cách đơn giản nhất.


2
IMO nói chung là tốt nếu cho rằng nó lớn. Tôi nghĩ rằng đó là niềm tin và thói quen đúng đắn để làm theo. Và vì ma quỷ không bao giờ ngủ, tôi không tin mình rằng tôi nhớ nơi tôi đã lặp lại trên một mảng. Nhưng bạn nói đúng, nếu chúng ta có 0 và 4 hằng số, việc lặp qua mảng có thể không gây ra bất kỳ vấn đề nào.
XVigyuri

1

Như bta đã nói, lý do là nó Range#eachgửi succđến đầu của nó, sau đó đến kết quả của succcuộc gọi đó , và cứ như vậy cho đến khi kết quả lớn hơn giá trị cuối. Bạn không thể nhận được từ 4 đến 0 bằng cách gọi succ, và trên thực tế, bạn đã bắt đầu lớn hơn kết thúc.


1

Tôi thêm một khả năng khác làm thế nào để nhận ra sự lặp lại trên Phạm vi đảo ngược. Tôi không sử dụng nó, nhưng nó là một khả năng. Có một chút rủi ro khi khỉ vá các vật thể có lõi bằng ruby.

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

Điều này làm việc cho trường hợp sử dụng lười biếng của tôi

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

0

OP đã viết

Có vẻ như là lạ đối với tôi rằng cấu trúc trên không tạo ra kết quả như mong đợi. Lý do cho điều đó là gì? Những tình huống nào khi hành vi này là hợp lý?

không phải là 'Nó có thể được thực hiện?' nhưng để trả lời câu hỏi chưa được hỏi trước khi chuyển sang câu hỏi thực sự đã được hỏi:

$ 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

Vì reverse_each được cho là xây dựng toàn bộ một mảng, nên rõ ràng downto sẽ hiệu quả hơn. Thực tế là một nhà thiết kế ngôn ngữ thậm chí có thể xem xét việc triển khai những thứ như vậy tương tự như câu trả lời cho câu hỏi thực tế như được hỏi.

Để trả lời câu hỏi như thực sự đã hỏi ...

Lý do là vì Ruby là một ngôn ngữ vô cùng ngạc nhiên. Một số ngạc nhiên là thú vị, nhưng có rất nhiều hành vi bị phá vỡ hoàn toàn. Ngay cả khi một số ví dụ sau đây được sửa chữa bởi các bản phát hành mới hơn, thì vẫn có rất nhiều ví dụ khác và chúng vẫn như những cáo buộc về tư duy của thiết kế ban đầu:

nil.to_s
   .to_s
   .inspect

kết quả là "" nhưng

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

kết quả trong

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

Bạn có thể mong đợi << và push giống nhau để thêm vào các mảng, nhưng

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

Bạn có thể mong đợi 'grep' hoạt động giống như dòng lệnh Unix tương đương của nó, nhưng nó === không khớp với = ~, mặc dù tên của nó.

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

Các phương pháp khác nhau là bí danh của nhau một cách bất ngờ, vì vậy bạn phải học nhiều tên cho cùng một thứ - ví dụ finddetect- ngay cả khi bạn giống như hầu hết các nhà phát triển và chỉ sử dụng cái này hay cái kia. Nhiều cùng đi cho size, countlength, ngoại trừ cho các lớp học mà xác định mỗi khác nhau, hoặc không xác định một hoặc hai ở tất cả.

Trừ khi ai đó đã triển khai một cái gì đó khác - như phương pháp cốt lõi tapđã được xác định lại trong các thư viện tự động hóa khác nhau để nhấn một cái gì đó trên màn hình. Chúc may mắn khi tìm ra điều gì đang xảy ra, đặc biệt nếu một số mô-đun được yêu cầu bởi một số mô-đun khác đã đóng một mô-đun khác để làm điều gì đó không có tài liệu.

Đối tượng biến môi trường, ENV không hỗ trợ 'hợp nhất', vì vậy bạn phải viết

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

Như một phần thưởng, bạn thậm chí có thể xác định lại các hằng số của mình hoặc của người khác nếu bạn thay đổi ý định về những gì chúng nên là.


Điều này không trả lời câu hỏi theo bất kỳ cách nào, hình dạng hoặc hình thức nào. Nó không là gì ngoài một lời nói bóng gió về những điều mà tác giả không thích về Ruby.
Jörg W Mittag

Cập nhật để trả lời câu hỏi chưa được hỏi, ngoài câu trả lời đã thực sự được hỏi. rant: động từ 1. nói hoặc hét dài một cách tức giận, nóng nảy. Câu trả lời ban đầu không phải là tức giận hoặc nóng nảy: đó là một câu trả lời được cân nhắc với các ví dụ.
android.weasel

@ JörgWMittag Câu hỏi ban đầu cũng bao gồm: Có vẻ lạ đối với tôi rằng cấu trúc trên không tạo ra kết quả như mong đợi. Lý do cho điều đó là gì? Những tình huống nào khi hành vi này là hợp lý? vì vậy anh ấy tìm kiếm lý do, không phải giải pháp mã.
android.weasel

Một lần nữa, tôi không biết hoạt động của greptheo bất kỳ cách nào, hình dạng hoặc hình thức liên quan đến thực tế rằng việc lặp lại trên một phạm vi trống là một điều không nên. Tôi cũng không hiểu thực tế rằng việc lặp lại trên một phạm vi trống là một điều không cần thiết ở bất kỳ hình thức nào hoặc hình thức "vô cùng ngạc nhiên" và "hoàn toàn bị phá vỡ".
Jörg W Mittag,

Bởi vì phạm vi 4..0 có ý định rõ ràng là [4, 3, 2, 1, 0] nhưng đáng ngạc nhiên là thậm chí không đưa ra cảnh báo. Nó đã làm cho OP ngạc nhiên và nó làm tôi ngạc nhiên, và chắc chắn đã làm cho nhiều người khác ngạc nhiên. Tôi đã liệt kê những ví dụ khác về hành vi đáng ngạc nhiên. Tôi có thể trích dẫn thêm, nếu bạn thích. Một khi một thứ gì đó thể hiện nhiều hơn một số hành vi đáng ngạc nhiên nhất định, nó bắt đầu trôi vào lãnh thổ 'bị hỏng'. Một chút giống như cách các hằng số đưa ra cảnh báo khi bị ghi đè, đặc biệt là khi các phương thức không.
android.weasel

0

Đối với tôi, cách đơn giản nhất là:

[*0..9].reverse

Một cách khác để lặp lại để liệt kê:

(1..3).reverse_each{|v| p v}
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.