The list data structure (Starting out (for real))
-
Lists are heterogeneous and immutable:
A = [1, 2, 3]
Representation:
A +---+---+ +---+---+ +---+---+ | 1 | -----> | 2 | -----> | 3 | -----> [] +---+---+ +---+---+ +---+---+
-
Manipulating the beginning of a list is cheap:
> B = [0|A] [0, 1, 2, 3]
Representation:
B A +---+---+ +---+---+ +---+---+ +---+---+ | 0 | -----> | 1 | -----> | 2 | -----> | 3 | -----> [] +---+---+ +---+---+ +---+---+ +---+---+
-
Manipulating the end of a list is expensive:
> C = A ++ [0] [1, 2, 3, 0]
Representation:
A +---+---+ +---+---+ +---+---+ | 1 | -----> | 2 | -----> | 3 | -----> [] +---+---+ +---+---+ +---+---+ C +---+---+ +---+---+ +---+---+ +---+---+ | 1 | -----> | 2 | -----> | 3 | -----> | 0 | -----> [] +---+---+ +---+---+ +---+---+ +---+---+
-
Heads and tails:
> B = [0, 1, 2, 3]. > hd(B). 0 > tl(B). [1,2,3] > [Head|Tail] = B. [0,1,2,3] > Head 0 > Tail [1,2,3]
-
Operations on lists:
> length([1, 2, 3]). 3 > [1, 2] ++ [3, 4]. [1,2,3,4] > [1, 2, 3, 4] -- [2, 4]. [1,3] > lists:member(2, [1,2,3]). true > lists:reverse([1, 2, 3]). [3,2,1] > lists:flatten([[1, 2], [3], [[4]]]). [1,2,3,4] > lists:append([[1, 2], [3], [[4]]]). [1,2,3,[4]]
-
Pattern matching on lists:
case List of % Empty list [] -> ... % Non-empty list [H|T] -> ... end case List of % List with one element [Item] -> ... % List that starts with [1, 2, 3] [1, 2, 3] ++ _ -> ... % Long list _ when length(List) > 100 -> ... end
-
[integer()] % A list of integers [term()] % A list of anything [any()] % A list of anything list() % A list of anything [] % Empty list [integer()] | [float()] % A list of integers or a list of floats [integer() | float()] % A list of numbers
Note:
{atom(), integer()}
= A tuple with two elements. There is no equivalent syntax for lists.[atom()]
= A list of atoms. There is no equivalent syntax for tuples.
1.2 Recursion with lists (Recursion)
-
Lists and recursion. (mylist.erl contains the full source code.)
Naive recursion:
%% Return the length of a list (same as `length/1`) -spec len([term()]) -> non_neg_integer(). len([]) -> 0; len([_H|T]) -> 1 + len(T).
Tail recursive recursion:
%% Return the length of a list (same as `length/1`) %% (tail recursive solution) -spec len2([term()]) -> non_neg_integer(). len2(List) -> len2(List, 0). %% Calculate `len2(List) + Acc` -spec len2([term()], non_neg_integer()) -> non_neg_integer(). len2([], Acc) -> Acc; len2([_H|T], Acc) -> len2(T, Acc + 1).
Tail recursive recursion when the accumulator is a list:
%% Reverse a list (same as `lists:reverse/1`) -spec reverse([term()]) -> [term()]. reverse(List) -> reverse(List, []). %% Calculate `reverse(List) ++ Acc` -spec reverse([term()], [term()]) -> [term()]. reverse([], Acc) -> Acc; reverse([H|T], Acc) -> reverse(T, [H|Acc]).
This function works by building up the
Acc
variable as it is deconstructing the originalList
parameter:====================================================================== List +---+---+ +---+---+ +---+---+ | 1 | -----> | 2 | -----> | 3 | -----> [] +---+---+ +---+---+ +---+---+ Acc [] ====================================================================== List +---+---+ +---+---+ | 2 | -----> | 3 | -----> [] +---+---+ +---+---+ Acc +---+---+ | 1 | -----> [] +---+---+ ====================================================================== List +---+---+ | 3 | -----> [] +---+---+ Acc +---+---+ +---+---+ | 2 | -----> | 1 | -----> [] +---+---+ +---+---+ ====================================================================== List [] Acc +---+---+ +---+---+ +---+---+ | 3 | -----> | 2 | -----> | 1 | -----> [] +---+---+ +---+---+ +---+---+ ======================================================================
1.3 Strings (Starting out (for real))
-
Characters are numbers:
> $a. 97
-
Strings are lists:
> [97, 98, 99]. "abc" > [$a, $b, $c]. "abc" > "". []
-
Using
io:format
to print lists:% ~p: Try to be smart > io:format("~p~n", ["abc"]). "abc" > io:format("~p~n", ["ab\0"]). [97,98,0] % ~w: Always print as list of numbers > io:format("~w~n", ["abc"]). [97,98,99] > io:format("~w~n", ["ab\0"]). [97,98,0] % ~s: Always print as string > io:format("~s~n", ["abc"]). abc > io:format("~s~n", ["ab\0"]). ab^@
-
Pattern matching on strings:
case Str of % Empty string (i.e. empty list) "" -> ... % String that starts with "version-1.0-" "version-1.0-" ++ _ -> ... end
My favorite real-world example:
case lists:reverse(Filename) of "piz.dorptset." ++ _ -> do_something(); _ -> do_something_else() end
-
Binaries are usually better for strings than lists:
> <<"abc">>. <<"abc">>
- They are quicker to handle (except when modified)
- They use much less memory
-
Regular expressions:
Search:
> re:run("abc", "b"). {match,[{1,1}]} > re:run("abc", "b", []). {match,[{1,1}]} > re:run("abc", "b", [{capture, none}]). match
Replace:
> re:replace("abcd", "c", "[&]", [{return, list}]). "ab[c]d" > re:replace("abcd", "c", "[&]", [{return, binary}]). <<"ab[c]d">> > S = re:replace("abcd", "c", "[&]", []). [<<"ab">>,[<<"[">>,<<"c">>,<<"]">>]|<<"d">>] > io:format("~s~n", [S]). ab[c]d
-
iodata()
:- Faster to construct than either binaries or strings.
- Conversion to string:
unicode:characters_to_list(IoData)
- Conversion to binary:
unicode:characters_to_binary(IoData)
-
Conversions:
> list_to_integer("42"). 42 > list_to_float("3.14"). 3.14 > string:to_integer("42"). {42,[]} > string:to_float("3.14"). {3.14,[]}
They behave differently if the string is not a valid number.
-
Reading input:
> io:get_line("What is your name? "). What is your name? Joe "Joe\n"
-
char() % 0..1114111 string() = [char()] binary() % A sequence of bytes byte() % 0..255 iolist() = [byte() | binary() | iolist()] % Simplified iodata() = iolist() | binary()
Note:
white | black
= Either thewhite
or theblack
atom. There is no equivalent syntax for strings (or binaries).
- Étude 5-2: Using the
re
Module. - Étude 6: Lists.
- Bonus: Interlude section
- Bonus: Étude 5-1: Validating Input.