425 lines
8.9 KiB
Elixir
425 lines
8.9 KiB
Elixir
defmodule List do
|
|
@moduledoc """
|
|
Implements functions that only make sense for lists
|
|
and cannot be part of the Enum protocol. In general,
|
|
favor using the Enum API instead of List.
|
|
|
|
A decision was taken to delegate most functions to
|
|
Erlang's standard lib. Small performance gains
|
|
might be acquired by re-implementing them in Elixir.
|
|
"""
|
|
|
|
# Bifs: member/2, reverse/2
|
|
# Bifs: keymember/3, keysearch/3, keyfind/3
|
|
|
|
@doc """
|
|
Access the list via a predicate.
|
|
|
|
If a regular expression, it returns a list with the
|
|
matched contents.
|
|
|
|
If an atom, assumes the list is a keywords list and
|
|
access the key in the keywords equals to the given
|
|
atom.
|
|
|
|
This implements the same API as the `Access` protocol.
|
|
|
|
## Examples
|
|
|
|
list = [ :a, :b, :c ]
|
|
List.access list, -1 #=> :c
|
|
|
|
"""
|
|
def access(list, access) when is_list(list) do
|
|
Access.List.access(list, access)
|
|
end
|
|
|
|
@doc """
|
|
Given a list of lists, concatenates the sublists into a single list.
|
|
|
|
## Examples
|
|
|
|
List.concat [[1,[2],3], [4], [5,6]]
|
|
#=> [1,[2],3,4,5,6]
|
|
|
|
"""
|
|
def concat(list) when is_list(list) do
|
|
Erlang.lists.append(list)
|
|
end
|
|
|
|
@doc """
|
|
Concatenates the list on the right with the list on the left.
|
|
|
|
This function produces the same result the `++` operator. The only difference
|
|
is a minor optimization: when the first list contains only one element, we
|
|
simply add it as a head to the second list.
|
|
|
|
## Examples
|
|
|
|
List.concat [1,2,3], [4,5,6]
|
|
#=> [1,2,3,4,5,6]
|
|
|
|
"""
|
|
def concat([h], elements) when is_list(elements) do
|
|
[h|elements]
|
|
end
|
|
|
|
def concat(list, elements) when is_list(list) and is_list(elements) do
|
|
list ++ elements
|
|
end
|
|
|
|
@doc """
|
|
Flattens the given `list` of nested lists. An optional
|
|
tail can be given that will be added at the end of
|
|
the flattened list.
|
|
|
|
## Examples
|
|
|
|
List.flatten [1,[[2],3]]
|
|
# => [1,2,3]
|
|
|
|
List.flatten [1,[[2],3]], [4,5]
|
|
# => [1,2,3,4,5]
|
|
|
|
"""
|
|
def flatten(list) when is_list(list) do
|
|
Erlang.lists.flatten(list)
|
|
end
|
|
|
|
def flatten(list, tail) when is_list(list) and is_list(tail) do
|
|
Erlang.lists.flatten(list, tail)
|
|
end
|
|
|
|
@doc """
|
|
Folds (reduces) the given list to the left with
|
|
a function. Requires an accumulator.
|
|
|
|
## Examples
|
|
|
|
List.foldl [5,5], 10, fn(x, acc) -> x + acc end
|
|
#=> 20
|
|
|
|
List.foldl [1,2,3,4], 0, fn(x, acc) -> x - acc end
|
|
#=> 2
|
|
|
|
"""
|
|
def foldl(list, acc, function) when is_list(list) and is_function(function) do
|
|
Erlang.lists.foldl(function, acc, list)
|
|
end
|
|
|
|
@doc """
|
|
Folds (reduces) the given list to the right with
|
|
a function. Requires an accumulator.
|
|
|
|
## Examples
|
|
|
|
List.foldr [1,2,3,4], 0, fn(x, acc) -> x - acc end
|
|
#=> -2
|
|
|
|
"""
|
|
def foldr(list, acc, function) when is_list(list) and is_function(function) do
|
|
Erlang.lists.foldr(function, acc, list)
|
|
end
|
|
|
|
@doc """
|
|
Reverses the given list. This function simply delegates
|
|
to `lists:reverse` which is implemented in C for performance.
|
|
|
|
## Examples
|
|
|
|
List.reverse [1,2,3]
|
|
#=> [3,2,1]
|
|
|
|
"""
|
|
defdelegate [reverse: 1], to: Erlang.lists
|
|
|
|
@doc """
|
|
Returns the last element in `list` or nil if the `list` is empty.
|
|
|
|
## Examples
|
|
|
|
List.last []
|
|
#=> nil
|
|
List.last [1]
|
|
#=> 1
|
|
List.last [1, 2, 3]
|
|
#=> 3
|
|
|
|
"""
|
|
def last([]), do: nil
|
|
|
|
defdelegate [last: 1], to: Erlang.lists
|
|
|
|
@doc """
|
|
Checks if the given `term` is included in the list.
|
|
This function simply delegates to `lists:member`
|
|
which is implemented in C for performance.
|
|
|
|
## Examples
|
|
|
|
List.member? [1,2,3], 1
|
|
#=> true
|
|
|
|
List.member? [1,2,3], 0
|
|
#=> false
|
|
|
|
"""
|
|
def member?(list, term) when is_list(list) do
|
|
Erlang.lists.member(term, list)
|
|
end
|
|
|
|
@doc """
|
|
Prepends the items from the list of the left to the list on the right.
|
|
Note that items are prepended in reverse order. This function does not modify
|
|
the tail and therefore does not duplicate the entries in memory.
|
|
|
|
## Examples
|
|
|
|
List.prepend [1], [2, 3]
|
|
#=> [1,2,3]
|
|
|
|
List.prepend [1,0], [2, 3]
|
|
#=> [0,1,2,3]
|
|
|
|
"""
|
|
def prepend([h|t], other) do
|
|
prepend(t, [h|other])
|
|
end
|
|
|
|
def prepend([], other) do
|
|
other
|
|
end
|
|
|
|
@doc """
|
|
Returns a list of integers in the given range (both ends included when
|
|
possible). An optional step can be provided as well (defaults to 1).
|
|
|
|
If first > last and no step is provided, the numbers will be in descending
|
|
order.
|
|
|
|
## Examples
|
|
|
|
List.range 1, 3 #=> [1,2,3]
|
|
List.range 1, 8, 2 #=> [1,3,5,7]
|
|
List.range 1, 0 #=> []
|
|
List.range 3, 1 #=> [3,2,1]
|
|
List.range 5, 1, -2 #=> [5, 3, 1]
|
|
|
|
"""
|
|
def range(first, last, step // nil) when is_integer(first) and is_integer(last) and first <= last do
|
|
step = case step do
|
|
match: nil
|
|
Erlang.lists.seq(first, last, 1)
|
|
match: x when x < 0
|
|
[]
|
|
else:
|
|
Erlang.lists.seq(first, last, step)
|
|
end
|
|
end
|
|
|
|
def range(first, last, step) when is_integer(first) and is_integer(last) and first > last do
|
|
step = case step do
|
|
match: nil
|
|
Erlang.lists.seq(first, last, -1)
|
|
match: x when x > 0
|
|
[]
|
|
else:
|
|
Erlang.lists.seq(first, last, step)
|
|
end
|
|
end
|
|
|
|
@doc """
|
|
Sorts the list. Accepts an optional ordering function. fun(a, b) should
|
|
return true if `a` compares less than or equal to `b`, false otherwise.
|
|
|
|
## Examples
|
|
|
|
List.sort [3, 4, 2, 1, 7]
|
|
#=> [1, 2, 3, 4, 7]
|
|
|
|
List.sort [3, 4, 2, 1, 7], fn(a, b) -> b <= a end
|
|
#=> [7, 4, 3, 2, 1]
|
|
|
|
"""
|
|
def sort(list, fun // &1 <= &2) when is_list(list) and is_function(fun) do
|
|
:lists.sort fun, list
|
|
end
|
|
|
|
@doc """
|
|
Returns a list without duplicated items.
|
|
|
|
## Examples
|
|
|
|
List.uniq [1,2,3,2,1]
|
|
#=> [1,2,3]
|
|
|
|
"""
|
|
def uniq(list) when is_list(list) do
|
|
do_uniq(list, [])
|
|
end
|
|
|
|
@doc """
|
|
Duplicates the given element n times in a list.
|
|
|
|
## Examples
|
|
|
|
List.duplicate "hello", 3
|
|
#=> ["hello","hello","hello"]
|
|
|
|
List.duplicate [1,2], 2
|
|
#=> [[1,2],[1,2]]
|
|
"""
|
|
def duplicate(elem, n) when is_integer(n) do
|
|
Erlang.lists.duplicate(n, elem)
|
|
end
|
|
|
|
@doc """
|
|
Looks for a term in a list and returns its position.
|
|
If term is found in the first position, return 1.
|
|
For not terms not found in list, the return value is nil.
|
|
|
|
### Examples
|
|
|
|
List.find_index ['a'], 'b'
|
|
#=> nil
|
|
List.find_index ['a'], 'a'
|
|
#=> 1
|
|
"""
|
|
def find_index(list, term) do
|
|
index = Erlang.string.str(list, [term])
|
|
case index == 0 do
|
|
match: true
|
|
nil
|
|
match: false
|
|
index
|
|
end
|
|
end
|
|
|
|
@doc """
|
|
Wraps the argument in a list.
|
|
If the argument is already a list, returns the list.
|
|
If the argument is nil, returns an empty list.
|
|
|
|
## Examples
|
|
|
|
List.wrap [1,2,3] #=> [1,2,3]
|
|
|
|
"""
|
|
def wrap(list) when is_list(list) do
|
|
list
|
|
end
|
|
|
|
def wrap(nil) do
|
|
[]
|
|
end
|
|
|
|
def wrap(else) do
|
|
[else]
|
|
end
|
|
|
|
@doc """
|
|
Zips corresponding elements from two lists (or tuples) into one list of tuples. The
|
|
number of elements in the resulting list is equal to the length of the
|
|
shortest list among the given ones.
|
|
|
|
## Examples
|
|
|
|
List.zip [1, 2, 3], [4, 5, 6]
|
|
#=> [{1, 4}, {2, 5}, {3, 6}]
|
|
|
|
List.zip [1, 2], [4, 5, 6]
|
|
#=> [{1, 4}, {2, 5}]
|
|
|
|
"""
|
|
def zip(item1, item2) do
|
|
do_zip(to_list(item1), to_list(item2), [])
|
|
end
|
|
|
|
@doc """
|
|
Zips corresponding elements from each list in `list_of_lists`.
|
|
|
|
## Examples
|
|
|
|
List.zip [[1, 2], [3, 4], [5, 6]]
|
|
#=> [{1, 3, 5}, {2, 4, 6}]
|
|
|
|
List.zip [[1, 2], [3], [5, 6]]
|
|
#=> [{1, 3, 5}]
|
|
|
|
"""
|
|
def zip(list_of_lists) when is_list(list_of_lists) do
|
|
do_zip(list_of_lists, [])
|
|
end
|
|
|
|
@doc """
|
|
Unzips the given list of lists or tuples into separate lists and returns a
|
|
list of lists.
|
|
|
|
## Examples
|
|
|
|
List.unzip [{1, 2}, {3, 4}]
|
|
#=> [[1, 3], [2, 4]]
|
|
|
|
List.unzip [{1, :a, "apple"}, {2, :b, "banana"}, {3, :c}]
|
|
#=> [[1, 2, 3], [:a, :b, :c]]
|
|
|
|
"""
|
|
def unzip(list) when is_list(list) do
|
|
:lists.map tuple_to_list(&1), zip(list)
|
|
end
|
|
|
|
## Private
|
|
|
|
# uniq
|
|
|
|
defp do_uniq([h|t], acc) do
|
|
case Erlang.lists.member(h, acc) do
|
|
match: true
|
|
do_uniq(t, acc)
|
|
match: false
|
|
[h|do_uniq(t, [h|acc])]
|
|
end
|
|
end
|
|
|
|
defp do_uniq([], _acc) do
|
|
[]
|
|
end
|
|
|
|
# zip
|
|
|
|
defp do_zip([h1|t1], [h2|t2], acc) do
|
|
do_zip t1, t2, [{h1, h2}|acc]
|
|
end
|
|
|
|
defp do_zip(_, _, acc) do
|
|
reverse acc
|
|
end
|
|
|
|
defp do_zip(list, acc) do
|
|
converter = fn(x, acc) -> do_zip_each(to_list(x), acc) end
|
|
{mlist, heads} = :lists.mapfoldl converter, [], list
|
|
|
|
case heads do
|
|
match: nil
|
|
:lists.reverse acc
|
|
else:
|
|
do_zip mlist, [list_to_tuple(:lists.reverse(heads))|acc]
|
|
end
|
|
end
|
|
|
|
defp do_zip_each(_, nil) do
|
|
{ nil, nil }
|
|
end
|
|
|
|
defp do_zip_each([h|t], acc) do
|
|
{ t, [h|acc] }
|
|
end
|
|
|
|
defp do_zip_each([], _) do
|
|
{ nil, nil }
|
|
end
|
|
|
|
defp to_list(tuple) when is_tuple(tuple), do: tuple_to_list(tuple)
|
|
defp to_list(list) when is_list(list), do: list
|
|
end
|