Список — тип данных в Эрланге, предназначенный для группировки (упорядоченной) нескольких или многих термов, обычно одного типа, в новый, единый терм. То же: list.
Список создаётся с помощью квадратных скобок, внутри которых элементы перечислены через запятую. В следующем примере мы выстраиваем в последовательность несколько атомов, каждый из которых символизирует некоторое действие.
1> L = [sleep, eat, drink, sleep].
[sleep,eat,drink,sleep]
Хотя сама логика подсказывает нам, что в список лучше группировать однотипные элементы, чтобы ими можно было манипулировать в одном ключе, тем не менее в список можно группировать и разнотипные данные.
2> L2 = [sleep, 8, eat, 1, drink, 0.5, sleep, 2].
[sleep,8,eat,1,drink,0.5,sleep,2]
В этом примере вперемешку с атомами располагаются числа: целые и одно вещественное. Тут как бы подразумевается, что спать мы будем 8 часов, есть — 1 и т.д. Такой список тоже имеет право на существование: в нём после атома действия всегда следует какое-то число. Нам просто остаётся научиться обрабатывать такой список по два элемента — и вуаля! — для нас не будет проблемой то, что заранее количество элементов в списке неизвестно.
На списки очень похожи кортежи. Если списки лучше использовать в тех случаях, когда мы группируем повторяющиеся элементы, причём в неопределённом количестве, то кортежи используют в прочих случаях.
Элементом списка может быть совершенно любой терм. Например, фунтерм или пид. Список это тоже терм, и один список, конечно, может быть элементом другого списка:
1> L1 = [a, b, c].
[a,b,c]
2> L2 = [L1, d, e, f].
[[a,b,c],d,e,f]
Мы получили список L2, состоящий из четырёх элементов. Их них первый элемент это список [a,b,c]
.
Для создания пустого списка можно просто написать квадратные скобки без элементов: L = []
.
Для перепостроения списков есть очень удобная вещь, называемая обработчиком списка.
Допустим, мы не желаем, чтобы список вошёл в другой список в качестве единого элемента. Нам надо разложить элементы из одного списка и добавить к элементам другого списка. Делается это с помощью оператора |
.
1> L1 = [a, b, c].
[a,b,c]
2> L2 = [1, 2, 3 | L1].
[1,2,3,a,b,c]
С оператором |
следует обращаться осторожно. С его помощью легко создать так называемый “неправильный список”.
3> L3 = [1, 2, 3 | 4].
[1,2,3|4]
Как мы видим, если с помощью |
добавить не переменную, а литерал, тогда получается странный (неправильный) список [1,2,3|4]. С этим неправильным списком можно продолжать работать, снова при этом получая неправильные списки:
5> L5 = [0 | L3].
[0,1,2,3|4]
Неправильный список можно подвергнуть декомпозиции:
7> [A,B,C,D|E] = L5.
[0,1,2,3|4]
Первый элемент списка считается его головой. Остальная часть списка — хвостом.
При работе со списком приходится учитывать особенности работы с памятью. Доступ к голове — очень быстрая операция, поэтому как правило обрабатывают списки, начиная с головы. Это касается как добавления новых элементов — в голову, так и изъястие — из головы.
Частичную декомпозиции списка (получить его голову) можно с помощью того же оператора |
, задействовав его в паттерне.
1> L1 = [a, b, c, d, e].
[a,b,c,d,e]
2> [H | T] = L1.
[a,b,c,d,e]
3> H.
a
4> T.
[b,c,d,e]
Паттерн [H | T]
обладает, на первый взгляд, неопределённостью. Мало ли где можно поставить разделитель внутри длинного списка. Но на деле он всегда “отрубает” первый элемент. Поэтому мы и использовали такие буквы: H (head — голова) и T (tail — хвост).
С одной стороны, в Эрланге есть строковые литералы, с другой стороны, нет такого самостоятельного типа данных. Строки рассматриваются всего лишь как последовательности чисел. И на самом деле, строки это всего лишь синтаксический сахар.
Задавая строковые литералы на латинице и кириллице в оболочке, можно заметить различие.
1> Word = "hello".
"hello"
2> Slovo = "привет".
[1087,1088,1080,1074,1077,1090]
На самом деле, в обоих случаях мы получаем список из нескольких целых чисел: [104,101,108,108,111] и [1087,1088,1080,1074,1077,1090]. Разница в том, что оболочка в первом случае догадывается, что это текст, а во втором — нет.
Ещё два способа, как задать тот же список, который оболочка воспринимает как “hello”:
3> Word2 = [104, 101, 108, 108, 111].
"hello"
4> Word3 = [$h, $e, $l, $l, $o].
"hello"
Следует не забывать, что в Эрланге с помощью одинарных кавычек задаётся атом (не на латинице). Тут можно случайно ошибиться.
5> Slovo2 = 'привет'.
'привет'
6> is_atom(Slovo2).
true
Списки можно (и часто нужно) обрабатывать с помощью рекурсивной функции, то есть функции, которая вызывает саму себя. Типичный подход: в одной кляузе мы с помощью паттерна [H|T] “откусываем” по одному элементу, забирая их из головы списка, что-то делаем с этим элементом, снова внутри этой функции вызываем саму себя, передавая оставшийся хвост списка. Во второй кляузе мы фиксируем тот случай, когда список уже пуст, на этом рекурсия останавливается.
Давайте попрактикуемся в нумерологии и создадим функцию numerolog, которая получает какую-то строку (де факто список чисел, каждое из которых относится к символу юникода), складывает эти числа и возвращает значение.
main() ->
Text = "Александр Сергеевич Пушкин",
io:format("~p~n", [numerolog(Text, 0)]).
numerolog([], S) -> S;
numerolog([H|T], S) -> numerolog(T, S+H).
Наша программа распечатает число 25945.
В данном примере кляузы можно поменять местами — результат будет тот же. Когда функция numerolog/2 получит в качестве аргумента пустой список, шаблон [H|T] на нём не сработает.
Списки можно складывать и вычитать друг из друга — с помощью операторов ++
и --
.
1> [a,b,c,d] ++ [d,e,f,g,h].
[a,b,c,d,d,e,f,g,h]
2> "hello world" -- "ledz".
"hlo worl"
Со сложением списка всё очевидно: просто к первому списку “приставляется” второй. С вычитанием надо быть более внимательным. Из второго списка берётся первый элемент и ищется в первом списке. Как только находится, то удаляется. Удаляется только один раз. Чтобы удалить две буквы l в нашем примере, можно было бы написать lledz или ledzl (разницы нет). Потом во втором списке берётся второй элемент и так далее. Если какой-то элемент второго списка не найден в первом, это ни на что не влияет.
© Алексей Карманов, 2024.