Range — Elixir v1.16.3

Search documentation of Elixir

# View Source Range (Elixir v1.16.3)

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

## Types

limit()
step()
t()
t(first, last)

## Functions

disjoint?(range1, range2)

Checks if two ranges are disjoint.

new(first, last)

Creates a new range.

new(first, last, step)

Creates a new range with `step`.

shift(arg, steps_to_shift)

Shifts a range by the given number of steps.

size(range)

Returns the size of `range`.

split(range, split)

Splits a range in two.

to_list(arg1)

Converts a range to a list.

# Types

Link to this type

# limit()

View Source

    @type limit() :: integer()

Link to this type

# step()

View Source

    @type step() :: pos_integer() | neg_integer()

Link to this type

# t()

View Source

    @type t() :: %Range{first: limit(), last: limit(), step: step()}

Link to this type

# t(first, last)

View Source

    @type t(first, last) :: %Range{first: first, last: last, step: step()}

# Functions

Link to this function

# disjoint?(range1, range2)

View Source (since 1.8.0)

    @spec disjoint?(t(), t()) :: boolean()

Checks if two ranges are disjoint.

## Examples

    iex> Range.disjoint?(1..5, 6..9)
    true
    iex> Range.disjoint?(5..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

Link to this function

# new(first, last)

View Source

    @spec new(limit(), limit()) :: t()

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 behaviour is deprecated. Therefore, it is advised to explicitly list the step with `new/3`.

## Examples

    iex> Range.new(-100, 100)
    -100..100

Link to this function

# new(first, last, step)

View Source (since 1.12.0)

    @spec new(limit(), limit(), step()) :: t()

Creates a new range with `step`.

## Examples

    iex> Range.new(-100, 100, 2)
    -100..100//2

Link to this function

# shift(arg, steps_to_shift)

View Source (since 1.14.0)

    @spec shift(t(), integer()) :: t()

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

Link to this function

# size(range)

View Source (since 1.12.0)

    @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)
    10
    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

Link to this function

# split(range, split)

View Source (since 1.15.0)

    @spec split(t(), integer()) :: {t(), t()}

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)

Link to this function

# to_list(arg1)

View Source (since 1.15.0)

    @spec to_list(t()) :: [integer()]

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]