Computer >> 컴퓨터 >  >> 프로그래밍 >> Ruby

루비 레인지(Range) 완벽 정리: 동작 원리부터 실전 활용법까지

루비에서 레인지(Range)는 무엇이며, 어떻게 작동할까요?

레인지는 시작 값과 끝 값을 가지는 객체로, 두 값 사이의 전체 범위에 걸친 연속적인 시퀀스를 생성할 때 사용됩니다.

1에서 20까지, 또는 "a"에서 "z"까지 표현할 수 있습니다.

이 글에서 배울 내용:

  • 루비 레인지 생성 방법
  • stepinclude? 메서드 활용법
  • 레인지의 내부 동작 원리

그럼 바로 시작해 보겠습니다!

레인지 이해하기

참고로 루비 레인지의 기본 문법은 다음과 같습니다:

(1..20)

Range를 정의할 때 괄호는 필수가 아닙니다.

하지만 레인지에 메서드를 호출하려면 괄호가 필요합니다. 괄호가 없으면 레인지 자체가 아니라 레인지의 두 번째 요소에 메서드를 호출하게 되기 때문입니다.

Range 클래스는 Enumerable 모듈을 포함하고 있어서, 배열로 변환하지 않고도 강력한 반복(iteration) 메서드들을 모두 사용할 수 있습니다.

루비 step 메서드

Range에는 step처럼 유용한 메서드들이 있습니다.

예제:

(10..20).step(2).to_a
# [10, 12, 14, 16, 18, 20]

이 메서드를 사용하면 n 간격으로 레인지를 순회할 수 있습니다. 여기서 nstep에 전달하는 인자입니다.

특정 숫자가 레인지 안에 있는지 확인하는 방법

꼭 알아두어야 할 다른 Range 메서드로는 cover?include?가 있습니다.

두 메서드가 같은 일을 한다고 생각하면 안 됩니다. 실제로는 서로 다르게 동작하기 때문입니다.

include? 메서드는 이름 그대로 레인지에 해당 값이 포함되어 있는지 확인합니다. 즉, RangeArray로 펼친 후 그 안에 값이 존재하는지 검사하는 것과 동일합니다.

반면 cover?는 다릅니다. 단순히 레인지의 시작 값과 끝 값만 비교하기 때문에(begin <= obj <= end) 예상치 못한 결과가 나올 수 있습니다.

예제:

('a'..'z').include? "cc" # false
('a'..'z').cover? "cc"   # true

cover? 예제는 다음 코드와 동일합니다:

"a" <= "cc" && "cc" <= "z"

true가 반환되는 이유는 문자열이 문자별로(character by character) 비교되기 때문입니다. "a"가 "c"보다 앞에 오므로, 첫 번째 "c" 뒤의 문자들은 비교 결과에 영향을 주지 않습니다.

루비에서 레인지가 구현되는 방식

레인지는 숫자와 문자에만 국한되지 않습니다. <=>succ 메서드만 구현되어 있다면 어떤 객체든 레인지로 사용할 수 있습니다.

예를 들어 DateTime 레인지는 다음과 같이 만들 수 있습니다:

require 'time'

t1 = DateTime.new
t2 = DateTime.new + 30

next_30_days = t1..t2

# Example use
next_30_days.select(&:friday?).map(&:day)

그렇다면 이것은 어떻게 작동하는 걸까요? 간단한 구현 코드를 살펴보겠습니다:

def range(a, b)
  # if the first element is bigger than the second
  # then this isn't a sequential range
  return [] if a > b

  out = []

  # advance until the 2nd element is the same
  # as the first one
  while a != b
    out << a
    a = a.next
  end

  # add last element (inclusive range)
  # this also returns the results via implicit return
  out << a
end

p range 1, 10
p range 'a', 'z'

이해를 돕기 위해 주석을 추가했습니다.

핵심 아이디어는 첫 번째 객체에서 next 메서드를 계속 호출하여 두 번째 객체와 같아질 때까지 진행하는 것입니다. 두 객체는 결국 만날 것이라는 가정에 기반합니다.

커스텀 클래스에서 레인지 활용하기

대부분의 경우 숫자나 문자 레인지를 사용하게 되지만, 커스텀 클래스에서 레인지를 활용하는 방법도 알아두면 큰 도움이 됩니다.

예제:

class LetterMultiplier
  attr_reader :count

  include Comparable

  def initialize(letter, count)
    @letter = letter
    @count  = count
  end

  def succ
    self.class.new(@letter, @count + 1)
  end

  def <=>(other)
    count <=> other.count
  end
end

a = LetterMultiplier.new('w', 2)
b = LetterMultiplier.new('w', 8)

# Print array with all the items in the range
p Array(a..b)

여기서 핵심은 <=>succ 메서드를 올바르게 구현하는 것입니다.

include? 메서드를 사용하려면 Comparable 모듈을 포함(include)해야 합니다. 이 모듈은 <=> 메서드의 결과를 기반으로 하는 ==, <, > 같은 메서드들을 자동으로 추가해 줍니다.

마무리

이 글에서는 루비에서 레인지가 작동하는 방식을 살펴보았습니다. 이제 레인지를 더 깊이 이해하고, 레인지 연산을 지원하는 자신만의 객체를 직접 구현할 수 있을 것입니다.

다음 포스트를 놓치지 않으려면 아래 뉴스레터 구독도 잊지 마세요 🙂