Range 모듈
Range 모듈
범위(Range)는 공통 차이를 step이라 부르는, 0개·1개 또는 많은 오름차순·내림차순 정수의 시퀀스를 나타내요. 범위를 만들고 매칭하는 가장 흔한 형태는 Kernel에서 자동 import 되는 first..last와 first..last//step 표기법입니다.
출처: Range
본문
iex> 1 in 1..10
true
iex> 5 in 1..10
true
iex> 10 in 1..10
true
Elixir에서 범위는 항상 양끝을 포함합니다(inclusive). step이 정의되면 정수는 step에 맞아떨어질 때만 범위에 속합니다.
iex> 5 in 1..10//2
true
iex> 4 in 1..10//2
false
step 없이 범위를 정의하면 step은 범위의 첫·마지막 위치를 기준으로 정해져요. last >= first이면 step 1의 증가 범위가 되고, 그렇지 않으면 감소 범위가 됩니다. 다만 암묵적 감소 범위는 deprecated입니다. 따라서 3에서 1까지의 감소 범위가 필요하다면 3..1//-1처럼 쓰는 걸 권장합니다.
../0은 0..-1//1 범위, 일명 full-slice 범위를 만드는 단축키로도 쓸 수 있어요.
iex> ..
0..-1//1
사용 사례(Use cases)
범위는 Elixir에서 두 가지로 주로 쓰여요: 컬렉션으로서, 또는 다른 데이터 구조의 슬라이스를 나타내는 용도로요.
컬렉션으로서의 범위
Elixir의 범위는 enumerable이라 Enum 모듈과 함께 쓸 수 있어요.
iex> Enum.to_list(1..3)
[1, 2, 3]
iex> Enum.to_list(3..1//-1)
[3, 2, 1]
iex> Enum.to_list(1..5//2)
[1, 3, 5]
범위는 단일 요소를 가질 수도 있습니다.
iex> Enum.to_list(1..1)
[1]
iex> Enum.to_list(1..1//2)
[1]
아예 요소가 없을 수도 있어요.
iex> Enum.to_list(10..0//1)
[]
iex> Enum.to_list(0..10//-1)
[]
../0이 반환하는 full-slice 범위는 빈 컬렉션입니다.
iex> Enum.to_list(..)
[]
슬라이스로서의 범위
범위는 컬렉션을 슬라이스하는 데도 자주 쓰여요. 문자열이나 어떤 enumerable이든 슬라이스할 수 있습니다.
iex> String.slice("elixir", 1..4)
"lixi"
iex> Enum.slice([0, 1, 2, 3, 4, 5], 1..4)
[1, 2, 3, 4]
이런 경우 범위의 첫·마지막 값이 컬렉션의 위치에 매핑됩니다.
음수 값이 주어지면 뒤쪽에서부터 위치를 매핑해요.
iex> String.slice("elixir", 1..-2//1)
"lixi"
iex> Enum.slice([0, 1, 2, 3, 4, 5], 1..-2//1)
[1, 2, 3, 4]
../0이 반환하는 0..-1//1 범위는 컬렉션을 그대로 반환합니다. 그래서 full-slice 범위라고 불러요.
iex> String.slice("elixir", ..)
"elixir"
iex> Enum.slice([0, 1, 2, 3, 4, 5], ..)
[0, 1, 2, 3, 4, 5]
정의(Definition)
증가 범위 first..last//step은 first에서 last까지 step만큼 증가하는 범위이며, step은 양의 정수여야 하고 모든 값 v는 first <= v and v <= last여야 합니다. 따라서 10..0//1 범위는 10 <= v and v <= 0인 값 v가 없으므로 빈 범위예요.
마찬가지로 감소 범위 first..last//step은 first에서 last까지 step만큼 감소하는 범위이며, step은 음의 정수여야 하고 값 v는 first >= v and v >= last여야 합니다. 따라서 0..10//-1 범위는 0 >= v and v >= 10인 값 v가 없으므로 빈 범위입니다.
표현(Representation)
내부적으로 범위는 구조체로 표현됩니다.
iex> range = 1..9//2
1..9//2
iex> first..last//step = range
iex> first
1
iex> last
9
iex> step
2
iex> range.step
2
범위 필드(first, last, step)에 직접 접근할 수 있지만, 손으로 수정하거나 만들지는 말아야 해요. 대신 적절한 연산자나 new/2, new/3을 쓰세요.
범위는 모든 Enumerable 콜백의 메모리 효율적인 버전으로 Enumerable 프로토콜을 구현합니다.
iex> range = 1..10
1..10
iex> Enum.reduce(range, 0, fn i, acc -> i * i + acc end)
385
iex> Enum.count(range)
10
iex> Enum.member?(range, 11)
false
iex> Enum.member?(range, 8)
true
그런 함수 호출은 범위의 크기에 관계없이 메모리 효율적이에요. Enumerable 프로토콜의 구현은 오직 종점(endpoint)에만 기반한 로직을 사용하고 정수 리스트 전체를 실체화하지 않습니다.
타입(Types)
@type limit() :: integer()
@type step() :: pos_integer() | neg_integer()
@type t() :: %Range{first: limit(), last: limit(), step: step()}
@type t(first, last) :: %Range{first: first, last: last, step: step()}
함수(Functions)
disjoint?(range1, range2) (1.8.0부터)
@spec disjoint?(t(), t()) :: boolean()
두 범위가 서로소(disjoint)인지 확인합니다.
예시:
iex> Range.disjoint?(1..5, 6..9)
true
iex> Range.disjoint?(5..1//-1, 6..9)
true
iex> Range.disjoint?(1..5, 5..9)
false
iex> Range.disjoint?(1..5, 2..7)
false
범위가 서로소인지 계산할 때 step도 고려됩니다.
iex> Range.disjoint?(1..10//2, 2..10//2)
true
# First element in common is 29
iex> Range.disjoint?(1..100//14, 8..100//21)
false
iex> Range.disjoint?(57..-1//-14, 8..100//21)
false
iex> Range.disjoint?(1..100//14, 50..8//-21)
false
iex> Range.disjoint?(1..28//14, 8..28//21)
true
# First element in common is 14
iex> Range.disjoint?(2..28//3, 9..28//5)
false
iex> Range.disjoint?(26..2//-3, 29..9//-5)
false
# Starting from the back without alignment
iex> Range.disjoint?(27..11//-3, 30..0//-7)
true
new(first, last)
@spec new(limit(), limit()) :: t()
새 범위를 만듭니다. first가 last보다 작으면 범위는 first에서 last로 증가합니다. first가 last와 같으면 범위는 숫자 자체인 요소 하나를 포함합니다. first가 last보다 크면 범위는 first에서 last로 감소하는데, 이 동작은 deprecated예요. 따라서 new/3으로 step을 명시하는 걸 권장합니다.
iex> Range.new(-100, 100)
-100..100
new(first, last, step) (1.12.0부터)
@spec new(limit(), limit(), step()) :: t()
step으로 새 범위를 만듭니다.
iex> Range.new(-100, 100, 2)
-100..100//2
shift(range, steps_to_shift) (1.14.0부터)
@spec shift(t(), integer()) :: t()
범위를 주어진 steps_to_shift만큼 이동합니다.
iex> Range.shift(0..10, 1)
1..11
iex> Range.shift(0..10, 2)
2..12
iex> Range.shift(0..10//2, 2)
4..14//2
iex> Range.shift(10..0//-2, 2)
6..-4//-2
size(range) (1.12.0부터)
@spec size(t()) :: non_neg_integer()
range의 크기를 반환합니다.
iex> Range.size(1..10)
10
iex> Range.size(1..10//2)
5
iex> Range.size(1..10//3)
4
iex> Range.size(1..10//-1)
0
iex> Range.size(10..1//-1)
10
iex> Range.size(10..1//-2)
5
iex> Range.size(10..1//-3)
4
iex> Range.size(10..1//1)
0
split(range, split) (1.15.0부터)
@spec split(t(), integer()) :: {t(), t()}
범위를 둘로 나눕니다. 두 요소의 튜플을 반환해요. split이 범위의 요소 수보다 작으면 범위의 첫 요소가 split 항목을, 두 번째가 나머지 모든 항목을 가집니다. split이 범위의 요소 수보다 많으면 튜플의 두 번째 범위는 0개 요소를 내보냅니다.
증가 범위:
iex> Range.split(1..5, 2)
{1..2, 3..5}
iex> Range.split(1..5//2, 2)
{1..3//2, 5..5//2}
iex> Range.split(1..5//2, 0)
{1..-1//2, 1..5//2}
iex> Range.split(1..5//2, 10)
{1..5//2, 7..5//2}
감소 범위도 나눌 수 있어요.
iex> Range.split(5..1//-1, 2)
{5..4//-1, 3..1//-1}
iex> Range.split(5..1//-2, 2)
{5..3//-2, 1..1//-2}
iex> Range.split(5..1//-2, 0)
{5..7//-2, 5..1//-2}
iex> Range.split(5..1//-2, 10)
{5..1//-2, -1..1//-2}
빈 범위는 그 속성을 보존하면서도 여전히 빈 범위를 반환합니다.
iex> Range.split(2..5//-1, 2)
{2..3//-1, 4..5//-1}
iex> Range.split(2..5//-1, 10)
{2..3//-1, 4..5//-1}
iex> Range.split(5..2//1, 2)
{5..4//1, 3..2//1}
iex> Range.split(5..2//1, 10)
{5..4//1, 3..2//1}
나눌 숫자가 음수이면 뒤에서부터 나눕니다.
iex> Range.split(1..5, -2)
{1..3, 4..5}
iex> Range.split(5..1//-1, -2)
{5..3//-1, 2..1//-1}
음수이면서 범위의 요소 수보다 크면 튜플의 첫 요소가 빈 범위가 됩니다.
iex> Range.split(1..5, -10)
{1..0//1, 1..5}
iex> Range.split(5..1//-1, -10)
{5..6//-1, 5..1//-1}
속성(Properties)
범위를 나누면 다음 속성이 지켜집니다. split(input)이 {left, right}를 반환한다고 할 때:
assert input.first == left.first
assert input.last == right.last
assert input.step == left.step
assert input.step == right.step
assert Range.size(input) == Range.size(left) + Range.size(right)
to_list(arg1) (1.15.0부터)
@spec to_list(t()) :: [integer()]
범위를 리스트로 변환합니다.
iex> Range.to_list(0..5)
[0, 1, 2, 3, 4, 5]
iex> Range.to_list(-3..0)
[-3, -2, -1, 0]
더 알아보기
Kernel../2— 범위 연산자Enum— 범위를 비롯한 enumerable 함수String.slice/2— 범위로 문자열 슬라이스Enum.slice/2— 범위로 컬렉션 슬라이스