Elixir学习笔记——列表和元组

在本章中,我们将学习 Elixir 中最常用的两种集合数据类型:列表和元组。

(链接)列表

Elixir 使用方括号来指定值列表。值可以是任何类型:

iex>[1, 2, true, 3]

1, 2, true, 3

iex>length([1, 2, 3])

3

可以分别使用 ++/2 和 --/2 运算符连接或减去两个列表:

iex>[1, 2, 3] ++ [4, 5, 6]

1, 2, 3, 4, 5, 6

iex>[1, true, 2, false, 3, true] -- [true, false]

1, 2, 3, true

列表运算符永远不会修改现有列表。连接或从列表中删除元素会返回一个新列表。我们说 Elixir 数据结构是不可变的。不变性的一个优点是它可以使代码更清晰。您可以自由地传递数据,并保证没有人会在内存中改变它 - 只会对其进行转换。

在整个教程中,我们将大量讨论列表的头部和尾部。头部是列表的第一个元素,尾部是列表的其余部分。可以使用函数 hd/1 和 tl/1 检索它们。让我们将列表分配给变量并检索其头部和尾部:

iex>list = [1, 2, 3]

iex>hd(list)

1

iex>tl(list)

2, 3

获取空列表的头部或尾部会引发错误:

iex>hd([])

** (ArgumentError) 参数错误

有时您会创建一个列表,它会返回一个以 ~c 开头的带引号的值。例如:

iex>[11, 12, 13]

~c"\v\f\r"

iex>[104, 101, 108, 108, 111]

~c"hello"

当 Elixir 看到可打印的 ASCII 数字列表时,Elixir 会将其打印为字符列表(实际上是字符列表)。字符列表在与现有 Erlang 代码交互时非常常见。每当您在 IEx 中看到一个值并且不太确定它是什么时,您可以使用 i/1 来检索有关它的信息:

iex>i ~c"hello"

Term

i ~c"hello"

Data type

List

Description

...

Raw representation

104, 101, 108, 108, 111

Reference modules

List

Implemented protocols

...

我们将在"二进制文件、字符串和字符列表"一章中更多地讨论字符列表。

单引号字符串
在 Elixir 中,您还可以使用"hello"来构建字符列表,但此表示法在 Elixir v1.15 中已被软弃用,并将在未来版本中发出警告。最好改写为 ~c"hello"。

元组

Elixir 使用花括号来定义元组。与列表一样,元组可以保存任何值:

iex>{:ok, "hello"}
{:ok, "hello"}

iex>tuple_size({:ok, "hello"})
2

元组将元素连续存储在内存中。这意味着通过索引访问元组元素或获取元组大小是一项快速操作。索引从零开始:

iex>tuple = {:ok, "hello"}

{:ok, "hello"}

iex>put_elem(tuple, 1, "world")

{:ok, "world"}

iex>tuple

{:ok, "hello"}

也可以使用 put_elem/3 将元素放在元组中的特定索引处:

tuple = {:ok, "hello"}
{:ok, "hello"}
put_elem(tuple, 1, "world")
{:ok, "world"}
tuple
{:ok, "hello"}

请注意,put_elem/3 返回了一个新元组。存储在 tuple 变量中的原始元组未被修改。与列表一样,元组也是不可变的。对元组的每个操作都会返回一个新元组,它永远不会更改给定的元组。

列表还是元组?

列表和元组有什么区别?
列表以链接列表的形式存储在内存中,这意味着列表中的每个元素都保存其值并指向下一个元素,直到到达列表末尾。这意味着访问列表的长度是一个线性操作:我们需要遍历整个列表才能确定其大小。
类似地,列表连接的性能取决于左侧列表的长度:
iex>list = [1, 2, 3]

1, 2, 3

这很快,因为我们只需要遍历 `[0]` 即可将其添加到 `列表` 的前面

iex>[0] ++ list

0, 1, 2, 3

这很慢,因为我们需要遍历 `列表` 以附加 4

iex>list++ [4]

1, 2, 3, 4

另一方面,元组在内存中连续存储。这意味着获取元组大小或通过索引访问元素的速度很快。另一方面,更新或向元组添加元素的成本很高,因为它需要在内存中创建新的元组:

iex>tuple = {:a, :b, :c, :d}
{:a, :b, :c, :d}
iex>put_elem(tuple, 2, :e)
{:a, :b, :e, :d}

但请注意,元素本身不会被复制。更新元组时,除已替换的条目外,所有条目都在旧元组和新元组之间共享。此规则适用于 Elixir 中的大多数数据结构。这减少了语言需要执行的内存分配量,并且只有由于语言的不可变语义才有可能。

这些性能特征决定了这些数据结构的使用。简而言之,当返回的元素数量可能有所不同时,使用列表。元组具有固定大小。让我们看两个来自 String 模块的例子:

iex>String.split("hello world")

"hello", "world"

iex>String.split("hello beautiful world")

"hello", "beautiful", "world"

String.split/2 函数将字符串拆分为每个空格字符上的字符串列表。由于返回的元素数量取决于输入,因此我们使用列表。

另一方面,String.split_at/2 在给定位置将字符串拆分为两部分。由于它总是返回两个条目,而不管输入大小如何,因此它返回元组:

iex>String.split_at("hello world", 3)
{"hel", "lo world"}
iex>String.split_at("hello world", -4)
{"hello w", "orld"}

使用元组和原子创建"标记元组"也很常见,当操作可能成功或失败时,这是一个方便的返回值。例如,File.read/1 读取给定路径下文件的内容,该路径可能存在也可能不存在。它返回带标签的元组:

iex>File.read("path/to/existing/file")
{:ok, "... content ..."}
iex>File.read("path/to/unknown/file")
{:error, :enoent}

如果 File.read/1 给出的路径存在,它将返回一个元组,其中原子 :ok 作为第一个元素,文件内容作为第二个元素。否则,它将返回一个元组,其中包含 :error 和错误描述。我们很快就会了解到,Elixir 允许我们在带标签的元组上进行模式匹配,并轻松处理成功和失败的情况。

鉴于 Elixir 始终遵循这些规则,随着您学习和使用该语言,列表和元组之间的选择会变得更加清晰。Elixir 通常会指导您做正确的事情。例如,有一个 elem/2 函数用于访问元组项:

iex>tuple = {:ok, "hello"}
{:ok, "hello"}
iex>elem(tuple, 1)
"hello"

但是,由于您通常不知道列表中元素的数量,因此除了列表的头部之外,没有内置等效函数用于访问列表中的任意条目。

大小还是长度?

在计算数据结构中的元素时,Elixir 还遵循一个简单的规则:如果操作是恒定时间(值是预先计算的),则函数名为 size;如果操作是线性的(计算长度的速度随着输入的增长而变慢),则函数名为 length。为了便于记忆,"length"和"linear"都以"l"开头。

例如,到目前为止,我们已经使用了 4 个计数函数:byte_size/1(用于字符串中的字节数)、tuple_size/1(用于元组大小)、length/1(用于列表长度)和 String.length/1(用于字符串中的字素数)。我们使用 byte_size 来获取字符串中的字节数,这是一种廉价的操作。另一方面,检索 Unicode 字素的数量使用 String.length/1,并且可能很昂贵,因为它依赖于对整个字符串的遍历。

现在我们已经熟悉了语言中的基本数据类型,在讨论更复杂的数据结构之前,让我们先学习编写代码的重要结构。

相关推荐
程序员爱钓鱼几秒前
Go语言实战案例-创建模型并自动迁移
后端·google·go
javachen__6 分钟前
SpringBoot整合P6Spy实现全链路SQL监控
spring boot·后端·sql
_Kayo_4 小时前
node.js 学习笔记3 HTTP
笔记·学习
uzong6 小时前
技术故障复盘模版
后端
GetcharZp6 小时前
基于 Dify + 通义千问的多模态大模型 搭建发票识别 Agent
后端·llm·agent
桦说编程6 小时前
Java 中如何创建不可变类型
java·后端·函数式编程
IT毕设实战小研6 小时前
基于Spring Boot 4s店车辆管理系统 租车管理系统 停车位管理系统 智慧车辆管理系统
java·开发语言·spring boot·后端·spring·毕业设计·课程设计
wyiyiyi7 小时前
【Web后端】Django、flask及其场景——以构建系统原型为例
前端·数据库·后端·python·django·flask
CCCC13101637 小时前
嵌入式学习(day 28)线程
jvm·学习
星星火柴9368 小时前
关于“双指针法“的总结
数据结构·c++·笔记·学习·算法