Erlang List Comprehension

Erlang List Comprehension

Syntax:

1
[Expression || Generators1, Guards1, Generators2, ...]

Expression
The expression specifies the elements of the result. Example: [I || <– [1, 2, 3]] returns the input list element as is.

Generators
Generators create the data used in ther filter-map operations. A generator has the “Pattern <– Data” format, where “Data” is a list or an expression that results to a list and Pattern is a pattern used to match with the elements of the list. This pattern can be used to disassembly elements. Example two valid generators are I <– lists:seq(1, 10) and {X, Y} <– [{‘A’, ‘Excellent’}, {‘B’, ‘Good’}, {‘C’, ‘Fair’}].

Guards
Guards are expression that return either true or false, the same as the guards we have seen in the previous posts. They apply to the variables that are on the left of the guard and the ones that are accessible to the scope where the comprehension runs. Example: I <– [1, 2, 3, 4], I rem 1 == 0 is a valid generator.

Example:

I will show some examples that implements some list functions. The most of them already exists in the lists module as I did in the past, I will add these functions to the module called mylists.

map/2
The results contains the elements of the input list after applied to the input function.

1
2
3
4
5
6
7
8
9
% Generator: the items of the list provided
% Guard: no guard expression, all items are kept
% Expression: the item from the generator after applied to the Function

map(Function, List) –>
  [Function(I) || I <– List].

1> mylists:map(fun(I) –> 2 * I end, [1, 2, 3, 4, 5]).
[2,3,6,8,10]

deleteall/2
Deletes all occurrences of an element from the list.

1
2
3
4
5
6
7
8
9
% Generator: the items of the list provided
% Guard: the item should not be equal (both value and type) with the Elem
% Expression: keep the elements of the list that "pass" the guard test, as they are

deleteall(Elem, List) –>
  [I || I <– List, I =/= Elem].

1> mylists:deleteall(3, [1, 2, 3, 4, 3, 2, 1]).
[1,2,4,2,1]

partition/2
Partition a list into two, according to if the elements satisfy or not a given predicate.

1
2
3
4
5
6
7
8
9
10
partition(Pred, List) –>
  {[I || I <– List, Pred(I)], [I || I <– List, not(Pred(I))]}.

% an alternative implementation
partition2(Pred, List) –>
    Sat = filter(Pred, List),
    {Sat, List  Sat}.

1> mylists:partition(fun(I) –> is_atom(I) end, [1, a, 2, b, 3.0]).
{[a,b],[1,2,3.0]}

replicated/2
Creates a list of Items of length Times.

1
2
3
4
5
6
7
8
% Generator: only used for fixing the length
% Expression: a fixed item

replicated(Item, Times) –>
  [Item || _ <– lists:seq(1, Times)].

1> mylists:replicated(':–)', 10).
[':–)',':–)',':–)',':–)',':–)',':–)',':–)',':–)',':–)',':–)']

replicate_items/2
Replicates each elements of the list Times times.

1
2
3
4
5
6
7
8
9
replicate_items(Times, List) –>
  mylists:flatten([[Item || _ <– lists:seq(1, Times)] || Item <– List]).

% same as
% replicate_items(Times, List) –>
%   mylists:flatten([replicated(Item, Times) || Item <– List]).

1> mylists:replicate_items(3, [a, b, c]).
[a,a,a,b,b,b,c,c,c]

member/2
Returns true if an element is a member of the list, else returns false.

1
2
3
4
5
6
7
member(Elem, List) –>
  [] /= [ok || I <– List, I == Elem].

1> mylists:member(a, [3, 2, 1, a, x, z]).
true
2> mylists:member(a, [3, 2, 1, aa, x, z]).
false

member_times/2
Returns the number of occurences of an element in a list.

1
2
3
4
5
6
7
8
9
member_times(Elem, List) –>
  length([ok || I <– List, I == Elem]).

1> mylists:member_times(a, [1, a, 2, 3, b, a, c]).
2
2> mylists:member_times(a, [1, a, 2, 3, b, c]).
1
3> mylists:member_times(a, [1, 2, 3, b, c]).
0

quicksort/1
This is the famous Quicksort implementation that is often used to show the power and compactness of Erlang.

1
2
3
4
5
6
7
8
9
qsort([]) –>
  [];
qsort([Pivot | List]) –>
  qsort([I || I <– List, I < Pivot])
  ++
  [Pivot | qsort([I || I <– List, I >= Pivot])].

1> mylists:qsort([7, 1, 3, 9, 1, 2, 0, 4, 6, 5]).
[0,1,1,2,3,4,5,6,7,9]

Multiple Generators
Now I will present some examples with multiple generators.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
1> [{I, J} || I <– [1, 2, 3], J <– [a, b, c]].
[{1,a},{1,b},{1,c},{2,a},{2,b},{2,c},{3,a},{3,b},{3,c}]

% duplicate list
2> [I ||  <– [a, a], I <– [1, 2, 3]].
[1,2,3,1,2,3]

% duplicate elements
3> [I || I <– [1, 2, 3], J <– [a, a]].
[1,1,2,2,3,3]

% discard elements and duplicate the others
4> Discard = [2, 4].
[2,4]
5> [I || I <– [1, 2, 3, 4, 5, 6], not(lists:member(I, Discard)),  <– [a, a]].
[1,1,3,3,5,5,6,6]

% subsequences
8> [[I || I <– lists:seq(1, J)] || J <– lists:seq(1, 10)].
[[1],
 [1,2],
 [1,2,3],
 [1,2,3,4],
 [1,2,3,4,5],
 [1,2,3,4,5,6],
 [1,2,3,4,5,6,7],
 [1,2,3,4,5,6,7,8],
 [1,2,3,4,5,6,7,8,9],
 [1,2,3,4,5,6,7,8,9,10]]