查看源代码 Range (Elixir v1.16.2)

范围表示一个由零、一个或多个升序或降序整数组成的序列,这些整数具有一个称为步长的公差。

创建和匹配范围的最常见形式是通过 first..lastfirst..last//step 符号,它们自动从 Kernel 导入。

iex> 1 in 1..10
true
iex> 5 in 1..10
true
iex> 10 in 1..10
true

Elixir 中的范围总是包含边界。当定义了步长时,只有与步长匹配的整数才会属于范围。

iex> 5 in 1..10//2
true
iex> 4 in 1..10//2
false

在没有定义步长的情况下定义范围时,步长将根据范围的第一个和最后一个位置来定义。如果 last >= first,则它将是一个步长为 1 的递增范围。否则,它是一个递减范围。但是请注意,隐式递减范围已弃用。因此,如果您需要从 31 的递减范围,最好写成 3..1//-1

../0 也可以用作创建范围 0..-1//1 的快捷方式,它也被称为全切片范围。

iex> ..
0..-1//1

用例

范围在 Elixir 中通常有两种用途:作为集合或表示另一个数据结构的切片。

范围作为集合

Elixir 中的范围是可枚举的,因此可以与 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 返回的全切片范围是一个空集合。

iex> Enum.to_list(..)
[]

范围作为切片

范围也常用于切片集合。您可以切片字符串或任何可枚举的。

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..-1//1(由 ../0 返回)将返回完整的集合,这就是为什么它被称为全切片范围。

iex> String.slice("elixir", ..)
"elixir"
iex> Enum.slice([0, 1, 2, 3, 4, 5], ..)
[0, 1, 2, 3, 4, 5]

定义

递增范围 first..last//step 是从 firstlast 递增 step 的范围,其中 step 必须是正整数,并且所有值 v 必须满足 first <= v and v <= last。因此,范围 10..0//1 是一个空范围,因为没有值 v 满足 10 <= v and v <= 0

类似地,递减范围 first..last//step 是从 firstlast 递减 step 的范围,其中 step 必须是负整数,并且值 v 必须满足 first >= v and v >= last。因此,范围 0..10//-1 是一个空范围,因为没有值 v 满足 0 >= v and v >= 10

表示

在内部,范围表示为结构体。

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

您可以直接访问范围字段(firstlaststep),但您不应该手动修改或创建范围。相反,请使用适当的操作符或 new/2new/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 协议的实现使用仅基于端点的逻辑,并且不会实际生成整个整数列表。

摘要

函数

检查两个范围是否不相交。

创建一个新的范围。

使用 step 创建一个新的范围。

将范围按给定的步数进行偏移。

返回 range 的大小。

将范围拆分为两个。

将范围转换为列表。

类型

@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()}

函数

链接到此函数

disjoint?(range1, range2)

查看源代码 (自 1.8.0 起)
@spec disjoint?(t(), t()) :: boolean()

检查两个范围是否不相交。

示例

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

计算范围是否不相交时也会考虑步长。

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
@spec new(limit(), limit()) :: t()

创建一个新的范围。

如果 first 小于 last,则范围将从 first 递增到 last。如果 first 等于 last,则范围将包含一个元素,即该数字本身。

如果 first 大于 last,则范围将从 first 递减到 last,尽管此行为已弃用。因此,建议使用 new/3 显式列出步长。

示例

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(arg, steps_to_shift)

查看源代码 (自 1.14.0 起)
@spec shift(t(), integer()) :: t()

将范围按给定的步数进行偏移。

示例

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)
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
链接到此函数

split(range, split)

查看源代码 (自 1.15.0 起)
@spec split(t(), integer()) :: {t(), t()}

将范围拆分为两个。

它返回一个包含两个元素的元组。

如果 split 小于范围中元素的数量,则范围中的第一个元素将包含 split 个条目,而第二个元素将包含所有剩余的条目。

如果 split 大于范围中元素的数量,则元组中的第二个范围将发出零个元素。

示例

递增范围

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}

属性

当拆分范围时,将观察到以下属性。假设 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]