루비에서 "역 범위"를 반복 할 수없는 이유가 있습니까?


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

답변:


99

범위는 내용이 아니라 시작과 끝으로 정의되는 것입니다. 범위에 대한 "반복"은 일반적인 경우에 실제로 의미가 없습니다. 예를 들어, 두 날짜에 의해 생성 된 범위에서 "반복"하는 방법을 고려하십시오. 매일 반복 하시겠습니까? 월별? 연도 별? 주 단위로? 잘 정의되어 있지 않습니다. 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 월 1 일부터 8 월 16 일까지 반복한다는 것은 며칠 씩 걷는 것을 의미합니다. 나는 우리가 일반적으로 동의 할 수있는 것이 종종 있다고 생각합니다. 왜냐하면 우리는 직관적으로 그렇게 의미하기 때문입니다. 귀하의 답변에 감사 드리며 귀하가 제공 한 링크도 유용했습니다.
fifigyuri

3
저는 여전히 많은 범위에 대해 "직관적 인"반복을 정의하는 것이 일관되게 수행하기 어렵다고 생각하며, 그런 방식으로 날짜를 반복하는 것이 직관적으로 1 일과 동일한 단계를 의미한다는 데 동의하지 않습니다. 결국 하루 자체가 이미 범위 시간 (자정부터 자정까지). 예를 들어 "1 월 1 일부터 8 월 18 일까지"(정확히 20 주)가 며칠이 아니라 몇 주를 반복한다는 의미가 아니라고 누가 말할 수 있습니까? 시간, 분 또는 초 단위로 반복하지 않는 이유는 무엇입니까?
John Feminella

8
.each중복 이 있고 5.downto(1) { |n| puts n }잘 작동합니다. 또한 모든 r.first r.last 항목 대신 (6..10).reverse_each.
mk12 2010

@ Mk12 : 100 % 동의합니다. 저는 새로운 루비 스트를 위해 아주 노골적으로 보이려고 노력했습니다. 하지만 너무 혼란 스러울 수도 있습니다.
John Feminella 2014 년

양식에 연도를 추가하려고 할 때 다음을 사용했습니다.= f.select :model_year, (Time.zone.now.year + 1).downto(Time.zone.now.year - 100).to_a
Eric Norcross


18

Ruby에서 범위를 반복하면 범위 의 첫 번째 개체에 대한 메서드 가 each호출 succ됩니다.

$ 4.succ
=> 5

그리고 5는 범위 밖에 있습니다.

이 해킹으로 역 반복을 시뮬레이션 할 수 있습니다.

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

John은 이것이 0에 걸치면 작동하지 않을 것이라고 지적했습니다.

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

그들이 의도를 모호하게하기 때문에 내가 그들 중 어떤 것도 정말 좋아한다고 말할 수 없습니다.


2
아니요. .abs를 사용하는 대신 -1을 곱하면됩니다.
Jonas Elfström

12

"Programming Ruby"라는 책에 따르면 Range 개체는 범위의 두 끝점을 저장하고 .succ멤버를 사용하여 중간 값을 생성합니다. 범위에서 사용하는 데이터 유형의 종류에 따라 언제든지 멤버 의 하위 클래스를 Integer만들고 다시 정의하여 .succ역방향 반복자처럼 작동하도록 할 수 있습니다..next ).

Range를 사용하지 않고도 원하는 결과를 얻을 수 있습니다. 이 시도:

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

이것은 -1 단계에서 4에서 0으로 이동합니다. 그러나 Integer 인수를 제외하고 이것이 작동하는지 여부는 알 수 없습니다.




3

목록이 그렇게 크지 않은 경우. [*0..4].reverse.each { |i| puts i } 가장 간단한 방법 이라고 생각 합니다.


2
IMO는 일반적으로 그것이 크다고 가정하는 것이 좋습니다. 일반적으로 따르는 것이 올바른 믿음과 습관이라고 생각합니다. 그리고 악마는 절대 잠들지 않기 때문에 내가 어디에서 배열을 반복했는지 기억한다는 것을 믿지 않습니다. 그러나 당신이 옳습니다, 우리가 0과 4 상수를 가지고 있다면, 배열을 반복하는 것은 문제를 일으키지 않을 것입니다.
fifigyuri

1

bta가 말했듯이, 그 이유는 결과가 끝 값보다 클 때까지 처음으로 보낸 다음 해당 호출 의 결과로 Range#each보내는 것입니다. 을 호출하여 4에서 0까지 얻을 수 없으며 실제로 이미 끝보다 더 크게 시작합니다.succsuccsucc


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는 전체 배열을 구축한다고 주장되기 때문에 downto는 분명히 더 효율적일 것입니다. 언어 디자이너가 그런 것들을 구현하는 것을 고려할 수도 있다는 사실은 질문 한 실제 질문에 대한 답과 연결됩니다.

실제로 묻는 질문에 대답하려면 ...

그 이유는 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(".")'
[]

예 - 여러 같은 일의 이름 배워야 할 수 있도록 다양한 방법, 예기치 않게 서로 앨리어스 (alias) finddetect- 당신은 대부분의 개발자처럼 할 만 어느 하나 또는 다른를 사용하는 경우에도. 거의 같은 간다에 대한 size, count그리고length 다른 각을 정의, 또는 모두에서 하나 또는 두 개의 정의하지 않는 클래스를 제외하고.

누군가가 다른 것을 구현하지 않는 한-핵심 방법 tap이 다양한 자동화 라이브러리에서 재정의되어 화면에서 무언가를 누르는 것과 같습니다. 특히 다른 모듈에 필요한 일부 모듈이 문서화되지 않은 작업을 수행하기 위해 또 다른 모듈을 사용하는 경우에 무슨 일이 일어나고 있는지 알아내는 행운을 빕니다.

환경 변수 개체 인 ENV는 '병합'을 지원하지 않으므로 작성해야합니다.

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

보너스로, 자신이나 다른 사람의 상수가 무엇이어야하는지에 대한 마음이 바뀌면 다시 정의 할 수도 있습니다.


이것은 어떤 식 으로든, 모양이나 형태로 질문에 답하지 않습니다. 저자가 루비에 대해 싫어하는 것들에 대한 폭언 일뿐입니다.
Jörg W Mittag

실제로 요청 된 답변 외에도 요청되지 않은 질문에 대한 답변으로 업데이트되었습니다. rant : verb 1. 화나고 열정적 인 방식으로 길게 말하거나 외칩니다. 원래 답변은 화 나거나 열정적이지 않았습니다. 예를 들어 고려 된 답변이었습니다.
android.weasel

@ JörgWMittag 원래 질문에는 다음도 포함됩니다. 위의 구성이 예상 한 결과를 생성하지 않는 것이 이상하게 보입니다. 그 이유는 무엇입니까? 이 행동이 합리적 일 때 어떤 상황이 발생합니까? 그래서 그는 코드 솔루션이 아닌 이유를 추구합니다.
android.weasel

다시 말하지만, grep빈 범위를 반복 하는 것이 작동 하지 않는다는 사실과 관련하여 의 동작이 어떤 식 으로든, 모양 또는 형태가 어떤지 알 수 없습니다. 빈 범위를 반복하는 것이 작동하지 않는다는 사실이 어떤 식 으로든 "끝없이 놀라움"과 "완전히 깨짐"을 형성하는 방식을 알 수 없습니다.
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.