Range 클래스

Range 클래스

Range 객체는 주어진 시작(begin) 값과 끝(end) 값 사이에 있는 값들의 모임을 나타내요. Array, 문자열 등 여러 타입의 연속 값을 나타내는 데 쓰여요.

출처: Ruby 3.3 API

본문

Range 만들기

리터럴로 만들 수 있어요. ..는 끝 값을 포함하고, ...는 끝 값을 제외해요.

# Ranges that use '..' to include the given end value.
(1..4).to_a      # => [1, 2, 3, 4]
('a'..'d').to_a  # => ["a", "b", "c", "d"]
# Ranges that use '...' to exclude the given end value.
(1...4).to_a     # => [1, 2, 3]
('a'...'d').to_a # => ["a", "b", "c"]

Range.new 메서드로도 만들 수 있어요. 세 번째 인자 exclude_end로 끝 값을 제외하도록 정해요.

# Ranges that by default include the given end value.
Range.new(1, 4).to_a     # => [1, 2, 3, 4]
Range.new('a', 'd').to_a # => ["a", "b", "c", "d"]
# Ranges that use third argument +exclude_end+ to exclude the given end value.
Range.new(1, 4, true).to_a     # => [1, 2, 3]
Range.new('a', 'd', true).to_a # => ["a", "b", "c"]

시작 없는 범위 (Beginless Ranges)

beginless range는 명확한 끝 값은 있지만 시작 값이 nil이에요. 그러한 범위는 끝 값까지 모든 값을 포함해요.

r = (..4)               # => nil..4
r.begin                 # => nil
r.include?(-50)         # => true
r.include?(4)           # => true

r = (...4)              # => nil...4
r.include?(4)           # => false

Range.new(nil, 4)       # => nil..4
Range.new(nil, 4, true) # => nil...4

시작 없는 범위로 배열을 슬라이스할 수도 있어요:

a = [1, 2, 3, 4]
r = (..2) # => nil...2
a[r]      # => [1, 2]

시작 없는 범위의 each 메서드는 예외를 발생시켜요.

끝 없는 범위 (Endless Ranges)

endless range는 명확한 시작 값은 있지만 끝 값이 nil이에요. 그러한 범위는 시작 값부터 모든 값을 포함해요.

r = (1..)         # => 1..
r.end             # => nil
r.include?(50)    # => true

Range.new(1, nil) # => 1..

끝 없는 범위 리터럴은 두 점이나 세 점으로 쓸 수 있어요. 어느 쪽이든 같은 요소를 갖지만, 둘은 같지 않아요(not equal).

r0 = (1..)           # => 1..
r1 = (1...)          # => 1...
r0.begin == r1.begin # => true
r0.end == r1.end     # => true
r0 == r1             # => false

끝 없는 범위로 배열을 슬라이스할 수도 있어요:

a = [1, 2, 3, 4]
r = (2..) # => 2..
a[r]      # => [3, 4]

끝 없는 범위의 each 메서드는 주어진 블록을 무한히 호출해요:

a = []
r = (1..)
r.each do |i|
  a.push(i) if i.even?
  break if i > 10
end
a # => [2, 4, 6, 8, 10]

범위는 시작과 끝이 모두 없을 수도 있어요. 리터럴로 시작도 끝도 없는 범위를 쓸 때는 시작이나 끝 중 적어도 하나를 명시적 nil로 줘야 해요. Ruby가 Range#inspect에 쓰는 형태인, 명시적 nil 시작과 암묵적 nil 끝을 권장해요:

(nil..)    # => (nil..)
(..nil)    # => (nil..)
(nil..nil) # => (nil..)

Range와 다른 클래스 (Ranges and Other Classes)

객체의 클래스가 인스턴스 메서드 <=>를 구현하면 그 객체를 범위에 넣을 수 있어요. 그렇게 하는 Ruby 코어 클래스로 Array, Complex, File::Stat, Float, Integer, Kernel, Module, Numeric, Rational, String, Symbol, Time이 있어요.

t0 = Time.now         # => 2021-09-19 09:22:48.4854986 -0500
t1 = Time.now         # => 2021-09-19 09:22:56.0365079 -0500
t2 = Time.now         # => 2021-09-19 09:23:08.5263283 -0500
(t0..t2).include?(t1) # => true
(t0..t1).include?(t2) # => false

범위의 요소들이 인스턴스 메서드 succ를 구현할 때만 범위를 반복(iterate)할 수 있어요. 그렇게 하는 Ruby 코어 클래스로 Integer, String, Symbol이 있어요(위에서 언급한 다른 클래스는 아님). 반복자 메서드에는 Range 자체의 each, step, %와, Enumerable 모듈에서 포함된 each_entry, each_with_index, each_with_object, each_slice, each_cons, reverse_each가 있어요.

a = []
(1..4).each {|i| a.push(i) }
a # => [1, 2, 3, 4]

Range와 사용자 정의 클래스 (Ranges and User-Defined Classes)

범위에 쓸 사용자 정의 클래스는 인스턴스 <=>를 구현해야 해요(Integer#<=> 참고). 반복을 가능하게 하려면 인스턴스 메서드 succ도 구현해야 해요(Integer#succ 참고). 아래 클래스는 <=>succ를 모두 구현해서, 범위를 만들고 반복하는 데 모두 쓸 수 있어요. Comparable 모듈이 포함되어 == 메서드가 <=>의 관점에서 정의된다는 점에 주의하세요.

# Represent a string of 'X' characters.
class Xs
  include Comparable
  attr_accessor :length
  def initialize(n)
    @length = n
  end
  def succ
    Xs.new(@length + 1)
  end
  def <=>(other)
    @length <=> other.length
  end
  def to_s
    sprintf "%2d #{inspect}", @length
  end
  def inspect
    'X' * @length
  end
end

r = Xs.new(3)..Xs.new(6) #=> XXX..XXXXXX
r.to_a                   #=> [XXX, XXXX, XXXXX, XXXXXX]
r.include?(Xs.new(5))    #=> true
r.include?(Xs.new(7))    #=> false

Public Class Methods

json_create(object)

as_json을 참고하세요. JSON에서 Range 객체를 복원해요.

new(begin, end, exclude_end = false) → new_range

주어진 beginend 객체를 기반으로 새 범위를 반환해요. 선택 인자 exclude_end는 객체 end를 범위의 마지막 객체로 포함할지 정해요:

Range.new(2, 5).to_a            # => [2, 3, 4, 5]
Range.new(2, 5, true).to_a      # => [2, 3, 4]
Range.new('a', 'd').to_a        # => ["a", "b", "c", "d"]
Range.new('a', 'd', true).to_a  # => ["a", "b", "c"]

Public Instance Methods

%(n) {|element| ... } → self, %(n) → enumerator

self의 요소들을 반복해요. 블록이 주어지면 범위의 선택된 요소로 블록을 호출하고 self를 반환해요. 블록이 없으면 enumerator를 반환하는데, self가 숫자면 Enumerator::ArithmeticSequence 클래스, 아니면 Enumerator 클래스예요. Range#step과 관련돼요.

a = []
(1..5).%(2) {|element| a.push(element) } # => 1..5
a # => [1, 3, 5]
a = []
('a'..'e').%(2) {|element| a.push(element) } # => "a".."e"
a # => ["a", "c", "e"]

e = (1..5) % 2 # => ((1..5).%(2))
e.class        # => Enumerator::ArithmeticSequence

self == other → true or false

다음 조건을 모두 만족할 때만 true를 반환해요:

  • other가 range이다.
  • other.begin == self.begin
  • other.end == self.end
  • other.exclude_end? == self.exclude_end?

그 외에는 false를 반환해요.

r = (1..5)
r == (1..5)                # => true
r = Range.new(1, 5)
r == 'foo'                 # => false
r == (2..5)                # => false
r == (1...5)               # => false
r == Range.new(1, 5, true) # => false

같은 인자라도 ==eql?의 반환값이 다를 수 있어요:

(1..2) == (1..2.0)   # => true
(1..2).eql? (1..2.0) # => false

self === object → true or false

objectself.beginself.end 사이에 있으면 true, 그렇지 않으면 false를 반환해요.

(1..4) === 2       # => true
(1..4) === 5       # => false
(1..4) === 'a'     # => false
(1..4) === 4       # => true
(1...4) === 4      # => false
('a'..'d') === 'c' # => true
('a'..'d') === 'e' # => false

case 문은 메서드 ===를 사용해요:

case 79
when (1..50)
  "low"
when (51..75)
  "medium"
when (76..100)
  "high"
end # => "high"

as_json(*)

Range#as_jsonRange.json_create 메서드는 Range 객체를 직렬화/역직렬화하는 데 쓸 수 있어요. Range#as_jsonself를 직렬화해서 2-요소 해시를 반환해요:

require 'json/add/range'
x = (1..4).as_json     # => {"json_class"=>"Range", "a"=>[1, 4, false]}
y = (1...4).as_json    # => {"json_class"=>"Range", "a"=>[1, 4, true]}
z = ('a'..'d').as_json # => {"json_class"=>"Range", "a"=>["a", "d", false]}

JSON.create는 그런 해시를 역직렬화해서 Range 객체를 반환해요:

Range.json_create(x) # => 1..4
Range.json_create(y) # => 1...4
Range.json_create(z) # => "a".."d"

begin → object

self의 시작을 정의하는 객체를 반환해요. Range#first, Range#end와 관련돼요.

(1..4).begin # => 1
(..2).begin  # => nil

bsearch {|obj| block } → value

이진 탐색(binary search)으로 self에서 요소를 선택해 반환해요.

count → integer, count(object) → integer, count {|element| ... } → integer

인자나 블록 기준(주어진 경우)에 따라 요소의 개수를 반환해요.

인자도 블록도 없으면 요소의 수를 반환해요:

(1..4).count      # => 4
(1...4).count     # => 3
('a'..'d').count  # => 4
(1..).count       # => Infinity
(..4).count       # => Infinity

인자 object가 주어지면 self에서 찾은 object의 수를 반환해요(보통 0 또는 1):

(1..4).count(2)   # => 1
(1..4).count(5)   # => 0

블록이 주어지면 각 요소로 블록을 호출하고, 블록이 truthy 값을 반환한 요소의 수를 반환해요:

(1..4).count {|element| element < 3 } # => 2

cover?(object) → true or false, cover?(range) → true or false

주어진 인자가 self 안에 있으면 true, 그렇지 않으면 false를 반환해요.

범위가 아닌 인자 object<=<로 평가해요. 끝 값을 포함하는 범위(#exclude_end? == false)는 self.begin <= object <= self.end로, 끝 값을 제외하는 범위(#exclude_end? == true)는 r.begin <= object < r.end로 평가해요.

r = (1..4)
r.cover?(1)     # => true
r.cover?(4)     # => true
r.cover?(0)     # => false
r.cover?(5)     # => false

r = (1...4)
r.cover?(1)     # => true
r.cover?(3)     # => true
r.cover?(0)     # => false
r.cover?(4)     # => false

범위 인자 range가 주어지면 selfrange의 첫 번째·마지막 요소를 비교해요:

r = (1..4)
r.cover?(1..4)     # => true
r.cover?(0..4)     # => false
r.cover?(1..5)     # => false
r.cover?('a'..'d') # => false

r = (1...4)
r.cover?(1..3)     # => true
r.cover?(1..4)     # => false

시작과 끝이 숫자면 cover?include?처럼 동작해요. 하지만 숫자가 아니면 두 메서드는 다를 수 있어요:

(1..3).cover?(1.5) # => true
(1..3).include?(1.5) # => true

('a'..'d').cover?('cc')   # => true
('a'..'d').include?('cc') # => false

다음 중 하나면 false를 반환해요: self의 시작 값이 끝 값보다 큰 경우, 또는 내부 <=> 호출이 nil을 반환해 비교할 수 없는 경우. Range#include?와 관련돼요.

each {|element| ... } → self, each → an_enumerator

블록이 주어지면 self의 각 요소를 블록으로 전달해요. self.first.respond_to?(:succ)가 아니면 예외를 발생시켜요. 블록이 없으면 enumerator를 반환해요.

a = []
(1..4).each {|element| a.push(element) } # => 1..4
a # => [1, 2, 3, 4]

end → object

self의 끝을 정의하는 객체를 반환해요. Range#begin, Range#last와 관련돼요.

(1..4).end  # => 4
(1...4).end # => 4
(1..).end   # => nil

entries

to_a의 별칭이에요.

eql?(other) → true or false

다음 조건을 모두 만족할 때만 true를 반환해요:

  • other가 range이다.
  • other.begin eql? self.begin
  • other.end eql? self.end
  • other.exclude_end? == self.exclude_end?
r = (1..5)
r.eql?(1..5)                  # => true
r = Range.new(1, 5)
r.eql?('foo')                 # => false
r.eql?(2..5)                  # => false
r.eql?(1...5)                 # => false

exclude_end? → true or false

self가 자신의 끝 값을 제외하면 true, 아니면 false를 반환해요.

Range.new(2, 5).exclude_end?       # => false
Range.new(2, 5, true).exclude_end? # => true
(2..5).exclude_end?                # => false
(2...5).exclude_end?               # => true

first → object, first(n) → array

인자 없이 호출하면 self의 첫 번째 요소를 반환해요(있을 때). 음이 아닌 정수 n이 주어지면 배열에서 첫 n개 요소를 반환해요. 첫 요소가 없으면 예외를 발생시켜요.

(1..4).first     # => 1
('a'..'d').first # => "a"

(1..10).first(3) # => [1, 2, 3]
(1..4).first(50) # => [1, 2, 3, 4]

(..4).first # Raises RangeError

hash → integer

self의 정수 해시 값을 반환해요. 두 range 객체 r0r1r0.eql?(r1)일 때만 같은 해시 값을 가져요.

include?(object) → true or false (member?의 별칭)

objectself의 요소면 true, 아니면 false를 반환해요:

(1..4).include?(2)        # => true
(1..4).include?(5)        # => false
(1..4).include?(4)        # => true
(1...4).include?(4)       # => false
('a'..'d').include?('b')  # => true
('a'..'d').include?('e')  # => false
('a'...'d').include?('d') # => false

시작과 끝이 숫자면 include?cover?처럼 동작하지만, 숫자가 아니면 두 메서드는 다를 수 있어요.

inspect → string

begin.inspectend.inspect를 포함해 self의 문자열 표현을 반환해요:

(1..4).inspect  # => "1..4"
(1...4).inspect # => "1...4"
(1..).inspect   # => "1.."
(..4).inspect   # => "..4"

to_sinspect의 반환은 다를 수 있어요:

('a'..'d').to_s    # => "a..d"
('a'..'d').inspect # => "\"a\"..\"d\""

last → object, last(n) → array

인자 없이 호출하면 self의 마지막 요소를 반환해요(있을 때). exclude_end?true여도 인자 없이 lastself의 끝 요소를 반환해요:

(1..4).last     # => 4
(1...4).last    # => 4

음이 아닌 정수 n이 주어지면 배열에서 마지막 n개 요소를 반환해요. exclude_end?true이면 인자와 함께 쓴 last는 끝 요소를 반환하지 않아요:

(1..10).last(3) # => [8, 9, 10]
(1...4).last(3) # => [1, 2, 3]

마지막 요소가 없으면 (1..).last처럼 예외를 발생시켜요.

max → object, max(n) → array, max {|a, b| ... } → object, max(n) {|a, b| ... } → array

메서드 <=> 또는 주어진 블록을 비교에 사용해 self의 최댓값을 반환해요. 인자도 블록도 없으면 self의 최대값 요소를 반환해요:

(1..4).max     # => 4
('a'..'d').max # => "d"
(-4..-1).max   # => -1

음이 아닌 정수 n이 주어지고 블록이 없으면, 배열에서 n개의 최대값 요소를 반환해요:

(1..4).max(2)     # => [4, 3]
('a'..'d').max(2) # => ["d", "c"]
(1..4).max(50)    # => [4, 3, 2, 1]

블록이 주어지면 먼저 self의 처음 두 요소로, 그다음에는 지금까지의 최대값과 다음 요소로 순차적으로 호출돼요. n이 0이면 빈 배열을 반환해요. self가 endless 범위((1..))이거나, 블록이 주어졌는데 beginless 범위면 예외를 발생시켜요. Range#min, Range#minmax와 관련돼요.

member?

include?의 별칭이에요.

min → object, min(n) → array, min {|a, b| ... } → object, min(n) {|a, b| ... } → array

메서드 <=> 또는 주어진 블록을 비교에 사용해 self의 최솟값을 반환해요. max와 대칭적으로 동작해요:

(1..4).min     # => 1
('a'..'d').min # => "a"
(-4..-1).min   # => -4

(1..4).min(2)     # => [1, 2]
('a'..'d').min(2) # => ["a", "b"]
(1..4).min(50)    # => [1, 2, 3, 4]

self가 beginless 범위((..4))이거나, 블록이 주어졌는데 endless 범위면 예외를 발생시켜요. Range#max, Range#minmax와 관련돼요.

minmax → [object, object], minmax {|a, b| ... } → [object, object]

비교 메서드 <=> 또는 주어진 블록에 따라 self의 최솟값과 최댓값을 담은 2-요소 배열을 반환해요.

(1..4).minmax     # => [1, 4]
(1...4).minmax    # => [1, 3]
('a'..'d').minmax # => ["a", "d"]
(-4..-1).minmax   # => [-4, -1]

(1..4).minmax {|a, b| -(a <=> b) } # => [4, 1]

self가 beginless 또는 endless 범위면 예외를 발생시켜요. 비어 있을 땐 [nil, nil]을 반환해요. Range#min, Range#max와 관련돼요.

overlap?(range) → true or false

rangeself와 겹치면 true, 아니면 false를 반환해요:

(0..2).overlap?(1..3) #=> true
(0..2).overlap?(3..4) #=> false
(0..).overlap?(..0)   #=> true

범위가 아닌 인자에는 TypeError를 발생시켜요. 내부 <=> 호출이 nil을 반환하면 false를 반환해요:

(1..3).overlap?('a'..'d')  # => false

selfrange가 비어 있으면 false를 반환해요. "빈 범위"란 시작 값이 끝 값보다 크거나(또는 exclusive 범위일 때 같거나) 한 경우를 뜻해요. Range#cover?와 관련돼요.

reverse_each {|element| ... } → self, reverse_each → an_enumerator

블록이 주어지면 self의 각 요소를 역순으로 블록으로 전달해요. 블록이 없으면 enumerator를 반환해요.

a = []
(1..4).reverse_each {|element| a.push(element) } # => 1..4
a # => [4, 3, 2, 1]

a = []
(1...4).reverse_each {|element| a.push(element) } # => 1...4
a # => [3, 2, 1]

size → non_negative_integer or Infinity or nil

시작과 끝 값이 모두 숫자면 self의 요소 수를 반환하고, 아니면 nil을 반환해요:

(1..4).size      # => 4
(1...4).size     # => 3
(1..).size       # => Infinity
('a'..'z').size  #=> nil

Range#count와 관련돼요.

step(n = 1) {|element| ... } → self, step(n = 1) → enumerator

self의 요소들을 반복해요. 블록이 주어지고 인자가 없으면 범위의 각 요소로 블록을 호출하고 self를 반환해요. 양의 정수 인자 n이 주어지면 요소 0, 요소 n, 요소 2n 식으로 블록을 호출해요. 블록이 없으면 enumerator를 반환하는데, self가 숫자면 Enumerator::ArithmeticSequence 클래스, 아니면 Enumerator 클래스예요.

a = []
(1..5).step {|element| a.push(element) } # => 1..5
a # => [1, 2, 3, 4, 5]

a = []
(1..5).step(2) {|element| a.push(element) } # => 1..5
a # => [1, 3, 5]

e = (1..5).step(2) # => ((1..5).step(2))
e.class            # => Enumerator::ArithmeticSequence

to_a → array (entries의 원본)

self의 요소들을 배열로 반환해요. entries로도 불려요.

to_s → string

self의 문자열 표현을 반환해요(to_s 사용). inspect와 다를 수 있어요.

to_json(*args)

self를 나타내는 JSON 문자열을 반환해요:

require 'json/add/range'
(1..4).to_json  # => "{\"json_class\":\"Range\",\"a\":[1,4,false]}"