View Source Range (Elixir v1.17.0-rc.1)
Ranges represent a sequence of zero, one or many, ascending or descending integers with a common difference called step.
The most common form of creating and matching on ranges is
via the first..last
and first..last//step
notations, auto-imported from Kernel
:
iex> 1 in 1..10
true
iex> 5 in 1..10
true
iex> 10 in 1..10
true
Ranges are always inclusive in Elixir. When a step is defined, integers will only belong to the range if they match the step:
iex> 5 in 1..10//2
true
iex> 4 in 1..10//2
false
When defining a range without a step, the step will be
defined based on the first and last position of the
range, If last >= first
, it will be an increasing range
with a step of 1. Otherwise, it is a decreasing range.
Note, however, implicit decreasing ranges are deprecated.
Therefore, if you need a decreasing range from 3
to 1
,
prefer to write 3..1//-1
instead.
../0
can also be used as a shortcut to create the range 0..-1//1
,
also known as the full-slice range:
iex> ..
0..-1//1
Use cases
Ranges typically have two uses in Elixir: as a collection or to represent a slice of another data structure.
Ranges as collections
Ranges in Elixir are enumerables and therefore can be used
with the Enum
module:
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]
Ranges may also have a single element:
iex> Enum.to_list(1..1)
[1]
iex> Enum.to_list(1..1//2)
[1]
Or even no elements at all:
iex> Enum.to_list(10..0//1)
[]
iex> Enum.to_list(0..10//-1)
[]
The full-slice range, returned by ../0
, is an empty collection:
iex> Enum.to_list(..)
[]
Ranges as slices
Ranges are also frequently used to slice collections. You can slice strings or any enumerable:
iex> String.slice("elixir", 1..4)
"lixi"
iex> Enum.slice([0, 1, 2, 3, 4, 5], 1..4)
[1, 2, 3, 4]
In those cases, the first and last values of the range are mapped to positions in the collections.
If a negative number is given, it maps to a position from the back:
iex> String.slice("elixir", 1..-2//1)
"lixi"
iex> Enum.slice([0, 1, 2, 3, 4, 5], 1..-2//1)
[1, 2, 3, 4]
The range 0..-1//1
, returned by ../0
, returns the
collection as is, which is why it is called the full-slice
range:
iex> String.slice("elixir", ..)
"elixir"
iex> Enum.slice([0, 1, 2, 3, 4, 5], ..)
[0, 1, 2, 3, 4, 5]
Definition
An increasing range first..last//step
is a range from first
to last
increasing by step
where step
must be a positive
integer and all values v
must be first <= v and v <= last
.
Therefore, a range 10..0//1
is an empty range because there
is no value v
that is 10 <= v and v <= 0
.
Similarly, a decreasing range first..last//step
is a range
from first
to last
decreasing by step
where step
must
be a negative integer and values v
must be first >= v and v >= last
.
Therefore, a range 0..10//-1
is an empty range because there
is no value v
that is 0 >= v and v >= 10
.
Representation
Internally, ranges are represented as structs:
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
You can access the range fields (first
, last
, and step
)
directly but you should not modify nor create ranges by hand.
Instead use the proper operators or new/2
and new/3
.
Ranges implement the Enumerable
protocol with memory
efficient versions of all Enumerable
callbacks:
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
Such function calls are efficient memory-wise no matter the
size of the range. The implementation of the Enumerable
protocol uses logic based solely on the endpoints and does
not materialize the whole list of integers.
Summary
Functions
Checks if two ranges are disjoint.
Creates a new range.
Creates a new range with step
.
Shifts a range by the given number of steps.
Returns the size of range
.
Splits a range in two.
Converts a range to a list.
Types
@type limit() :: integer()
@type step() :: pos_integer() | neg_integer()
@type t(first, last) :: %Range{first: first, last: last, step: step()}
Functions
Checks if two ranges are disjoint.
Examples
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
Steps are also considered when computing the ranges to be disjoint:
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
Creates a new range.
If first
is less than last
, the range will be increasing from
first
to last
. If first
is equal to last
, the range will contain
one element, which is the number itself.
If first
is greater than last
, the range will be decreasing from first
to last
, albeit this behavior is deprecated. Therefore, it is advised to
explicitly list the step with new/3
.
Examples
iex> Range.new(-100, 100)
-100..100
Creates a new range with step
.
Examples
iex> Range.new(-100, 100, 2)
-100..100//2
Shifts a range by the given number of steps.
Examples
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
@spec size(t()) :: non_neg_integer()
Returns the size of range
.
Examples
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
Splits a range in two.
It returns a tuple of two elements.
If split
is less than the number of elements in the range, the first
element in the range will have split
entries and the second will have
all remaining entries.
If split
is more than the number of elements in the range, the second
range in the tuple will emit zero elements.
Examples
Increasing ranges:
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}
Decreasing ranges can also be split:
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}
Empty ranges preserve their property but still return empty ranges:
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}
If the number to split is negative, it splits from the back:
iex> Range.split(1..5, -2)
{1..3, 4..5}
iex> Range.split(5..1//-1, -2)
{5..3//-1, 2..1//-1}
If it is negative and greater than the elements in the range, the first element of the tuple will be an empty range:
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
When a range is split, the following properties are observed.
Given split(input)
returns {left, right}
, we have:
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)
Converts a range to a list.
Examples
iex> Range.to_list(0..5)
[0, 1, 2, 3, 4, 5]
iex> Range.to_list(-3..0)
[-3, -2, -1, 0]