Files
n8n-openai-adapter/lib/elixir/lib/enum.ex
T
José Valim 9e80f3b82b Add guard to impl of Enumerable for Function
This improves the stacktrace in case of wrong dispatch.
2014-05-30 15:59:17 +02:00

2205 lines
54 KiB
Elixir

defprotocol Enumerable do
@moduledoc """
Enumerable protocol used by `Enum` and `Stream` modules.
When you invoke a function in the `Enum` module, the first argument
is usually a collection that must implement this protocol. For example,
the expression
Enum.map([1, 2, 3], &(&1 * 2))
invokes underneath `Enumerable.reduce/3` to perform the reducing
operation that builds a mapped list by calling the mapping function
`&(&1 * 2)` on every element in the collection and cons'ing the
element with an accumulated list.
Internally, `Enum.map/2` is implemented as follows:
def map(enum, fun) do
reducer = fn x, acc -> {:cont, [fun.(x)|acc]} end
Enumerable.reduce(enum, {:cont, []}, reducer) |> elem(1) |> :lists.reverse()
end
Notice the user given function is wrapped into a `reducer` function.
The `reducer` function must return a tagged tuple after each step,
as described in the `acc/0` type.
The reason the accumulator requires a tagged tuple is to allow the
reducer function to communicate to the underlying enumerable the end
of enumeration, allowing any open resource to be properly closed. It
also allows suspension of the enumeration, which is useful when
interleaving between many enumerables is required (as in zip).
Finally, `Enumerable.reduce/3` will return another tagged tuple,
as represented by the `result/0` type.
"""
@typedoc """
The accumulator value for each step.
It must be a tagged tuple with one of the following "tags":
* `:cont` - the enumeration should continue
* `:halt` - the enumeration should halt immediately
* `:suspend` - the enumeration should be suspended immediately
Depending on the accumulator value, the result returned by
`Enumerable.reduce/3` will change. Please check the `result`
type docs for more information.
In case a reducer function returns a `:suspend` accumulator,
it must be explicitly handled by the caller and never leak.
"""
@type acc :: {:cont, term} | {:halt, term} | {:suspend, term}
@typedoc """
The reducer function.
Should be called with the collection element and the
accumulator contents. Returns the accumulator for
the next enumeration step.
"""
@type reducer :: (term, term -> acc)
@typedoc """
The result of the reduce operation.
It may be *done* when the enumeration is finished by reaching
its end, or *halted*/*suspended* when the enumeration was halted
or suspended by the reducer function.
In case a reducer function returns the `:suspend` accumulator, the
`:suspended` tuple must be explicitly handled by the caller and
never leak. In practice, this means regular enumeration functions
just need to be concerned about `:done` and `:halted` results.
Furthermore, a `:suspend` call must always be followed by another call,
eventually halting or continuing until the end.
"""
@type result :: {:done, term} | {:halted, term} | {:suspended, term, continuation}
@typedoc """
A partially applied reduce function.
The continuation is the closure returned as a result when
the enumeration is suspended. When invoked, it expects
a new accumulator and it returns the result.
A continuation is easily implemented as long as the reduce
function is defined in a tail recursive fashion. If the function
is tail recursive, all the state is passed as arguments, so
the continuation would simply be the reducing function partially
applied.
"""
@type continuation :: (acc -> result)
@doc """
Reduces the collection into a value.
Most of the operations in `Enum` are implemented in terms of reduce.
This function should apply the given `reducer` function to each
item in the collection and proceed as expected by the returned accumulator.
As an example, here is the implementation of `reduce` for lists:
def reduce(_, {:halt, acc}, _fun), do: {:halted, acc}
def reduce(list, {:suspend, acc}, fun), do: {:suspended, acc, &reduce(list, &1, fun)}
def reduce([], {:cont, acc}, _fun), do: {:done, acc}
def reduce([h|t], {:cont, acc}, fun), do: reduce(t, fun.(h, acc), fun)
"""
@spec reduce(t, acc, reducer) :: result
def reduce(collection, acc, fun)
@doc """
Checks if a value exists within the collection.
It should return `{:ok, boolean}`.
If `{:error, __MODULE__}` is returned a default algorithm using `reduce` and
the match (`===`) operator is used. This algorithm runs in linear time.
Please force use of the default algorithm unless you can implement an
algorithm that is significantly faster.
"""
@spec member?(t, term) :: {:ok, boolean} | {:error, module}
def member?(collection, value)
@doc """
Retrieves the collection's size.
It should return `{:ok, size}`.
If `{:error, __MODULE__}` is returned a default algorithm using `reduce` and
the match (`===`) operator is used. This algorithm runs in linear time.
Please force use of the default algorithm unless you can implement an
algorithm that is significantly faster.
"""
@spec count(t) :: {:ok, non_neg_integer} | {:error, module}
def count(collection)
end
defmodule Enum do
import Kernel, except: [max: 2, min: 2]
@moduledoc """
Provides a set of algorithms that enumerate over collections according to the
`Enumerable` protocol:
iex> Enum.map([1, 2, 3], fn(x) -> x * 2 end)
[2,4,6]
Some particular types, like dictionaries, yield a specific format on
enumeration. For dicts, the argument is always a `{key, value}` tuple:
iex> dict = %{a: 1, b: 2}
iex> Enum.map(dict, fn {k, v} -> {k, v * 2} end)
[a: 2, b: 4]
Note that the functions in the `Enum` module are eager: they always start
the enumeration of the given collection. The `Stream` module allows
lazy enumeration of collections and provides infinite streams.
Since the majority of the functions in `Enum` enumerate the whole
collection and return a list as result, infinite streams need to
be carefully used with such functions, as they can potentially run
forever. For example:
Enum.each Stream.cycle([1,2,3]), &IO.puts(&1)
"""
@compile :inline_list_funcs
@type t :: Enumerable.t
@type element :: any
@type index :: non_neg_integer
@type default :: any
# Require Stream.Reducers and its callbacks
require Stream.Reducers, as: R
defmacrop cont(_, entry, acc) do
quote do: {:cont, [unquote(entry)|unquote(acc)]}
end
defmacrop acc(h, n, _) do
quote do: {unquote(h), unquote(n)}
end
defmacrop cont_with_acc(f, entry, h, n, _) do
quote do
{:cont, {[unquote(entry)|unquote(h)], unquote(n)}}
end
end
@doc """
Invokes the given `fun` for each item in the `collection` and returns `false`
if at least one invocation returns `false`. Otherwise returns `true`.
## Examples
iex> Enum.all?([2, 4, 6], fn(x) -> rem(x, 2) == 0 end)
true
iex> Enum.all?([2, 3, 4], fn(x) -> rem(x, 2) == 0 end)
false
If no function is given, it defaults to checking if
all items in the collection evaluate to `true`.
iex> Enum.all?([1, 2, 3])
true
iex> Enum.all?([1, nil, 3])
false
"""
@spec all?(t) :: boolean
@spec all?(t, (element -> as_boolean(term))) :: boolean
def all?(collection, fun \\ fn(x) -> x end)
def all?(collection, fun) when is_list(collection) do
do_all?(collection, fun)
end
def all?(collection, fun) do
Enumerable.reduce(collection, {:cont, true}, fn(entry, _) ->
if fun.(entry), do: {:cont, true}, else: {:halt, false}
end) |> elem(1)
end
@doc """
Invokes the given `fun` for each item in the `collection` and returns `true` if
at least one invocation returns `true`. Returns `false` otherwise.
## Examples
iex> Enum.any?([2, 4, 6], fn(x) -> rem(x, 2) == 1 end)
false
iex> Enum.any?([2, 3, 4], fn(x) -> rem(x, 2) == 1 end)
true
If no function is given, it defaults to checking if
at least one item in the collection evaluates to `true`.
iex> Enum.any?([false, false, false])
false
iex> Enum.any?([false, true, false])
true
"""
@spec any?(t) :: boolean
@spec any?(t, (element -> as_boolean(term))) :: boolean
def any?(collection, fun \\ fn(x) -> x end)
def any?(collection, fun) when is_list(collection) do
do_any?(collection, fun)
end
def any?(collection, fun) do
Enumerable.reduce(collection, {:cont, false}, fn(entry, _) ->
if fun.(entry), do: {:halt, true}, else: {:cont, false}
end) |> elem(1)
end
@doc """
Finds the element at the given index (zero-based).
Returns `default` if index is out of bounds.
## Examples
iex> Enum.at([2, 4, 6], 0)
2
iex> Enum.at([2, 4, 6], 2)
6
iex> Enum.at([2, 4, 6], 4)
nil
iex> Enum.at([2, 4, 6], 4, :none)
:none
"""
@spec at(t, integer) :: element | nil
@spec at(t, integer, default) :: element | default
def at(collection, n, default \\ nil) do
case fetch(collection, n) do
{:ok, h} -> h
:error -> default
end
end
@doc """
Shortcut to `chunk(coll, n, n)`.
"""
@spec chunk(t, non_neg_integer) :: [list]
def chunk(coll, n), do: chunk(coll, n, n, nil)
@doc """
Returns a collection of lists containing `n` items each, where
each new chunk starts `step` elements into the collection.
`step` is optional and, if not passed, defaults to `n`, i.e.
chunks do not overlap. If the final chunk does not have `n`
elements to fill the chunk, elements are taken as necessary
from `pad` if it was passed. If `pad` is passed and does not
have enough elements to fill the chunk, then the chunk is
returned anyway with less than `n` elements. If `pad` is not
passed at all or is `nil`, then the partial chunk is discarded
from the result.
## Examples
iex> Enum.chunk([1, 2, 3, 4, 5, 6], 2)
[[1, 2], [3, 4], [5, 6]]
iex> Enum.chunk([1, 2, 3, 4, 5, 6], 3, 2)
[[1, 2, 3], [3, 4, 5]]
iex> Enum.chunk([1, 2, 3, 4, 5, 6], 3, 2, [7])
[[1, 2, 3], [3, 4, 5], [5, 6, 7]]
iex> Enum.chunk([1, 2, 3, 4, 5, 6], 3, 3, [])
[[1, 2, 3], [4, 5, 6]]
"""
@spec chunk(t, non_neg_integer, non_neg_integer) :: [list]
@spec chunk(t, non_neg_integer, non_neg_integer, t | nil) :: [list]
def chunk(coll, n, step, pad \\ nil) when n > 0 and step > 0 do
limit = :erlang.max(n, step)
{_, {acc, {buffer, i}}} =
Enumerable.reduce(coll, {:cont, {[], {[], 0}}}, R.chunk(n, step, limit))
if nil?(pad) || i == 0 do
:lists.reverse(acc)
else
buffer = :lists.reverse(buffer) ++ take(pad, n - i)
:lists.reverse([buffer|acc])
end
end
@doc """
Splits `coll` on every element for which `fun` returns a new value.
## Examples
iex> Enum.chunk_by([1, 2, 2, 3, 4, 4, 6, 7, 7], &(rem(&1, 2) == 1))
[[1], [2, 2], [3], [4, 4, 6], [7, 7]]
"""
@spec chunk_by(t, (element -> any)) :: [list]
def chunk_by(coll, fun) do
{_, {acc, res}} =
Enumerable.reduce(coll, {:cont, {[], nil}}, R.chunk_by(fun))
case res do
{buffer, _} ->
:lists.reverse([:lists.reverse(buffer) | acc])
nil ->
[]
end
end
@doc """
Given an enumerable of enumerables, concatenate the enumerables into a single list.
## Examples
iex> Enum.concat([1..3, 4..6, 7..9])
[1,2,3,4,5,6,7,8,9]
iex> Enum.concat([[1, [2], 3], [4], [5, 6]])
[1,[2],3,4,5,6]
"""
@spec concat(t) :: t
def concat(enumerables) do
do_concat(enumerables)
end
@doc """
Concatenates the enumerable on the right with the enumerable on the left.
This function produces the same result as the `Kernel.++/2` operator for lists.
## Examples
iex> Enum.concat(1..3, 4..6)
[1,2,3,4,5,6]
iex> Enum.concat([1, 2, 3], [4, 5, 6])
[1,2,3,4,5,6]
"""
@spec concat(t, t) :: t
def concat(left, right) when is_list(left) and is_list(right) do
left ++ right
end
def concat(left, right) do
do_concat([left, right])
end
defp do_concat(enumerable) do
fun = &[&1|&2]
reduce(enumerable, [], &reduce(&1, &2, fun)) |> :lists.reverse
end
@doc """
Returns the collection's size.
## Examples
iex> Enum.count([1, 2, 3])
3
"""
@spec count(t) :: non_neg_integer
def count(collection) when is_list(collection) do
:erlang.length(collection)
end
def count(collection) do
case Enumerable.count(collection) do
{:ok, value} when is_integer(value) ->
value
{:error, module} ->
module.reduce(collection, {:cont, 0}, fn
_, acc -> {:cont, acc + 1}
end) |> elem(1)
end
end
@doc """
Returns the count of items in the collection for which
`fun` returns `true`.
## Examples
iex> Enum.count([1, 2, 3, 4, 5], fn(x) -> rem(x, 2) == 0 end)
2
"""
@spec count(t, (element -> as_boolean(term))) :: non_neg_integer
def count(collection, fun) do
Enumerable.reduce(collection, {:cont, 0}, fn(entry, acc) ->
{:cont, if(fun.(entry), do: acc + 1, else: acc)}
end) |> elem(1)
end
@doc """
Drops the first `count` items from `collection`.
If a negative value `count` is given, the last `count`
values will be dropped. The collection is enumerated
once to retrieve the proper index and the remaining
calculation is performed from the end.
## Examples
iex> Enum.drop([1, 2, 3], 2)
[3]
iex> Enum.drop([1, 2, 3], 10)
[]
iex> Enum.drop([1, 2, 3], 0)
[1,2,3]
iex> Enum.drop([1, 2, 3], -1)
[1,2]
"""
@spec drop(t, integer) :: list
def drop(collection, count) when is_list(collection) and count >= 0 do
do_drop(collection, count)
end
def drop(collection, count) when count >= 0 do
res =
reduce(collection, count, fn
x, acc when is_list(acc) -> [x|acc]
x, 0 -> [x]
_, acc when acc > 0 -> acc - 1
end)
if is_list(res), do: :lists.reverse(res), else: []
end
def drop(collection, count) when count < 0 do
do_drop(reverse(collection), abs(count)) |> :lists.reverse
end
@doc """
Drops items at the beginning of `collection` while `fun` returns `true`.
## Examples
iex> Enum.drop_while([1, 2, 3, 4, 5], fn(x) -> x < 3 end)
[3,4,5]
"""
@spec drop_while(t, (element -> as_boolean(term))) :: list
def drop_while(collection, fun) when is_list(collection) do
do_drop_while(collection, fun)
end
def drop_while(collection, fun) do
{_, {res, _}} =
Enumerable.reduce(collection, {:cont, {[], true}}, R.drop_while(fun))
:lists.reverse(res)
end
@doc """
Invokes the given `fun` for each item in the `collection`.
Returns `:ok`.
## Examples
Enum.each(["some", "example"], fn(x) -> IO.puts x end)
"some"
"example"
#=> :ok
"""
@spec each(t, (element -> any)) :: :ok
def each(collection, fun) when is_list(collection) do
:lists.foreach(fun, collection)
:ok
end
def each(collection, fun) do
reduce(collection, nil, fn(entry, _) ->
fun.(entry)
nil
end)
:ok
end
@doc """
Returns `true` if the collection is empty, otherwise `false`.
## Examples
iex> Enum.empty?([])
true
iex> Enum.empty?([1, 2, 3])
false
"""
@spec empty?(t) :: boolean
def empty?(collection) when is_list(collection) do
collection == []
end
def empty?(collection) do
Enumerable.reduce(collection, {:cont, true}, fn(_, _) -> {:halt, false} end) |> elem(1)
end
@doc """
Finds the element at the given index (zero-based).
Returns `{:ok, element}` if found, otherwise `:error`.
A negative index can be passed, which means the collection is
enumerated once and the index is counted from the end (i.e.
`-1` fetches the last element).
## Examples
iex> Enum.fetch([2, 4, 6], 0)
{:ok, 2}
iex> Enum.fetch([2, 4, 6], 2)
{:ok, 6}
iex> Enum.fetch([2, 4, 6], 4)
:error
"""
@spec fetch(t, integer) :: {:ok, element} | :error
def fetch(collection, n) when is_list(collection) and n >= 0 do
do_fetch(collection, n)
end
def fetch(collection, n) when n >= 0 do
res =
Enumerable.reduce(collection, {:cont, 0}, fn(entry, acc) ->
if acc == n do
{:halt, entry}
else
{:cont, acc + 1}
end
end)
case res do
{:halted, entry} -> {:ok, entry}
{:done, _} -> :error
end
end
def fetch(collection, n) when n < 0 do
do_fetch(reverse(collection), abs(n + 1))
end
@doc """
Finds the element at the given index (zero-based).
Raises `OutOfBoundsError` if the given position
is outside the range of the collection.
## Examples
iex> Enum.fetch!([2, 4, 6], 0)
2
iex> Enum.fetch!([2, 4, 6], 2)
6
iex> Enum.fetch!([2, 4, 6], 4)
** (Enum.OutOfBoundsError) out of bounds error
"""
@spec fetch!(t, integer) :: element | no_return
def fetch!(collection, n) do
case fetch(collection, n) do
{:ok, h} -> h
:error -> raise Enum.OutOfBoundsError
end
end
@doc """
Filters the collection, i.e. returns only those elements
for which `fun` returns `true`.
## Examples
iex> Enum.filter([1, 2, 3], fn(x) -> rem(x, 2) == 0 end)
[2]
"""
@spec filter(t, (element -> as_boolean(term))) :: list
def filter(collection, fun) when is_list(collection) do
for item <- collection, fun.(item), do: item
end
def filter(collection, fun) do
Enumerable.reduce(collection, {:cont, []}, R.filter(fun))
|> elem(1) |> :lists.reverse
end
@doc """
Filters the collection and maps its values in one pass.
## Examples
iex> Enum.filter_map([1, 2, 3], fn(x) -> rem(x, 2) == 0 end, &(&1 * 2))
[4]
"""
@spec filter_map(t, (element -> as_boolean(term)), (element -> element)) :: list
def filter_map(collection, filter, mapper) when is_list(collection) do
for item <- collection, filter.(item), do: mapper.(item)
end
def filter_map(collection, filter, mapper) do
Enumerable.reduce(collection, {:cont, []}, R.filter_map(filter, mapper))
|> elem(1) |> :lists.reverse
end
@doc """
Returns the first item for which `fun` returns a truthy value. If no such
item is found, returns `ifnone`.
## Examples
iex> Enum.find([2, 4, 6], fn(x) -> rem(x, 2) == 1 end)
nil
iex> Enum.find([2, 4, 6], 0, fn(x) -> rem(x, 2) == 1 end)
0
iex> Enum.find([2, 3, 4], fn(x) -> rem(x, 2) == 1 end)
3
"""
@spec find(t, (element -> any)) :: element | nil
@spec find(t, default, (element -> any)) :: element | default
def find(collection, ifnone \\ nil, fun)
def find(collection, ifnone, fun) when is_list(collection) do
do_find(collection, ifnone, fun)
end
def find(collection, ifnone, fun) do
Enumerable.reduce(collection, {:cont, ifnone}, fn(entry, ifnone) ->
if fun.(entry), do: {:halt, entry}, else: {:cont, ifnone}
end) |> elem(1)
end
@doc """
Similar to `find/3`, but returns the value of the function
invocation instead of the element itself.
## Examples
iex> Enum.find_value([2, 4, 6], fn(x) -> rem(x, 2) == 1 end)
nil
iex> Enum.find_value([2, 3, 4], fn(x) -> rem(x, 2) == 1 end)
true
"""
@spec find_value(t, (element -> any)) :: any | :nil
@spec find_value(t, any, (element -> any)) :: any | :nil
def find_value(collection, ifnone \\ nil, fun)
def find_value(collection, ifnone, fun) when is_list(collection) do
do_find_value(collection, ifnone, fun)
end
def find_value(collection, ifnone, fun) do
Enumerable.reduce(collection, {:cont, ifnone}, fn(entry, ifnone) ->
fun_entry = fun.(entry)
if fun_entry, do: {:halt, fun_entry}, else: {:cont, ifnone}
end) |> elem(1)
end
@doc """
Similar to `find/3`, but returns the index (zero-based)
of the element instead of the element itself.
## Examples
iex> Enum.find_index([2, 4, 6], fn(x) -> rem(x, 2) == 1 end)
nil
iex> Enum.find_index([2, 3, 4], fn(x) -> rem(x, 2) == 1 end)
1
"""
@spec find_index(t, (element -> any)) :: index | :nil
def find_index(collection, fun) when is_list(collection) do
do_find_index(collection, 0, fun)
end
def find_index(collection, fun) do
res =
Enumerable.reduce(collection, {:cont, 0}, fn(entry, acc) ->
if fun.(entry), do: {:halt, acc}, else: {:cont, acc + 1}
end)
case res do
{:halted, entry} -> entry
{:done, _} -> nil
end
end
@doc """
Returns a new collection appending the result of invoking `fun`
on each corresponding item of `collection`.
The given function should return an enumerable.
## Examples
iex> Enum.flat_map([:a, :b, :c], fn(x) -> [x, x] end)
[:a, :a, :b, :b, :c, :c]
iex> Enum.flat_map([{1,3}, {4,6}], fn({x,y}) -> x..y end)
[1, 2, 3, 4, 5, 6]
"""
@spec flat_map(t, (element -> t)) :: list
def flat_map(collection, fun) do
reduce(collection, [], fn(entry, acc) ->
reduce(fun.(entry), acc, &[&1|&2])
end) |> :lists.reverse
end
@doc """
Maps and reduces a collection, flattening the given results.
It expects an accumulator and a function that receives each stream item
and an accumulator, and must return a tuple containing a new stream
(often a list) with the new accumulator or a tuple with `:halt` as first
element and the accumulator as second.
## Examples
iex> enum = 1..100
iex> n = 3
iex> Enum.flat_map_reduce(enum, 0, fn i, acc ->
...> if acc < n, do: {[i], acc + 1}, else: {:halt, acc}
...> end)
{[1,2,3], 3}
"""
@spec flat_map_reduce(t, acc, fun) :: {[any], any} when
fun: (element, acc -> {t, acc} | {:halt, acc}),
acc: any
def flat_map_reduce(collection, acc, fun) do
{_, {list, acc}} =
Enumerable.reduce(collection, {:cont, {[], acc}}, fn(entry, {list, acc}) ->
case fun.(entry, acc) do
{:halt, acc} ->
{:halt, {list, acc}}
{entries, acc} ->
{:cont, {reduce(entries, list, &[&1|&2]), acc}}
end
end)
{:lists.reverse(list), acc}
end
@doc """
Intersperses `element` between each element of the enumeration.
Complexity: O(n)
## Examples
iex> Enum.intersperse([1, 2, 3], 0)
[1, 0, 2, 0, 3]
iex> Enum.intersperse([1], 0)
[1]
iex> Enum.intersperse([], 0)
[]
"""
@spec intersperse(t, element) :: list
def intersperse(collection, element) do
list =
reduce(collection, [], fn(x, acc) ->
[x, element | acc]
end) |> :lists.reverse()
case list do
[] -> []
[_|t] -> t # Head is a superfluous intersperser element
end
end
@doc """
Inserts the given enumerable into a collectable.
## Examples
iex> Enum.into([1, 2], [0])
[0, 1, 2]
iex> Enum.into([a: 1, b: 2], %{})
%{a: 1, b: 2}
"""
@spec into(Enumerable.t, Collectable.t) :: Collectable.t
def into(collection, list) when is_list(list) do
list ++ to_list(collection)
end
def into(collection, %{} = map) when is_list(collection) and map_size(map) == 0 do
:maps.from_list(collection)
end
def into(collection, collectable) do
{initial, fun} = Collectable.into(collectable)
into(collection, initial, fun, fn x, acc ->
fun.(acc, {:cont, x})
end)
end
@doc """
Inserts the given enumerable into a collectable
according to the transformation function.
## Examples
iex> Enum.into([2, 3], [3], fn x -> x * 3 end)
[3, 6, 9]
"""
@spec into(Enumerable.t, Collectable.t, (term -> term)) :: Collectable.t
def into(collection, list, transform) when is_list(list) and is_function(transform, 1) do
list ++ map(collection, transform)
end
def into(collection, collectable, transform) when is_function(transform, 1) do
{initial, fun} = Collectable.into(collectable)
into(collection, initial, fun, fn x, acc ->
fun.(acc, {:cont, transform.(x)})
end)
end
defp into(collection, initial, fun, callback) do
try do
reduce(collection, initial, callback)
catch
kind, reason ->
stacktrace = System.stacktrace
fun.(initial, :halt)
:erlang.raise(kind, reason, stacktrace)
else
acc -> fun.(acc, :done)
end
end
@doc """
Joins the given `collection` according to `joiner`.
`joiner` can be either a binary or a list and the
result will be of the same type as `joiner`. If
`joiner` is not passed at all, it defaults to an
empty binary.
All items in the collection must be convertible
to a binary, otherwise an error is raised.
## Examples
iex> Enum.join([1, 2, 3])
"123"
iex> Enum.join([1, 2, 3], " = ")
"1 = 2 = 3"
"""
@spec join(t) :: String.t
@spec join(t, String.t) :: String.t
def join(collection, joiner \\ "")
def join(collection, joiner) when is_binary(joiner) do
reduced = reduce(collection, :first, fn
entry, :first -> to_string(entry)
entry, acc -> acc <> joiner <> to_string(entry)
end)
if reduced == :first do
""
else
reduced
end
end
@doc """
Returns a new collection, where each item is the result
of invoking `fun` on each corresponding item of `collection`.
For dicts, the function expects a key-value tuple.
## Examples
iex> Enum.map([1, 2, 3], fn(x) -> x * 2 end)
[2, 4, 6]
iex> Enum.map([a: 1, b: 2], fn({k, v}) -> {k, -v} end)
[a: -1, b: -2]
"""
@spec map(t, (element -> any)) :: list
def map(collection, fun) when is_list(collection) do
for item <- collection, do: fun.(item)
end
def map(collection, fun) do
Enumerable.reduce(collection, {:cont, []}, R.map(fun)) |> elem(1) |> :lists.reverse
end
@doc """
Maps and joins the given `collection` in one pass.
`joiner` can be either a binary or a list and the
result will be of the same type as `joiner`. If
`joiner` is not passed at all, it defaults to an
empty binary.
All items in the collection must be convertible
to a binary, otherwise an error is raised.
## Examples
iex> Enum.map_join([1, 2, 3], &(&1 * 2))
"246"
iex> Enum.map_join([1, 2, 3], " = ", &(&1 * 2))
"2 = 4 = 6"
"""
@spec map_join(t, (element -> any)) :: String.t
@spec map_join(t, String.t, (element -> any)) :: String.t
def map_join(collection, joiner \\ "", mapper)
def map_join(collection, joiner, mapper) when is_binary(joiner) do
reduced = reduce(collection, :first, fn
entry, :first -> to_string(mapper, entry)
entry, acc -> acc <> joiner <> to_string(mapper, entry)
end)
if reduced == :first do
""
else
reduced
end
end
@doc """
Invokes the given `fun` for each item in the `collection`
while also keeping an accumulator. Returns a tuple where
the first element is the mapped collection and the second
one is the final accumulator.
For dicts, the first tuple element must be a `{key, value}`
tuple.
## Examples
iex> Enum.map_reduce([1, 2, 3], 0, fn(x, acc) -> {x * 2, x + acc} end)
{[2, 4, 6], 6}
"""
@spec map_reduce(t, any, (element, any -> any)) :: any
def map_reduce(collection, acc, fun) when is_list(collection) do
:lists.mapfoldl(fun, acc, collection)
end
def map_reduce(collection, acc, fun) do
{list, acc} = reduce(collection, {[], acc}, fn(entry, {list, acc}) ->
{new_entry, acc} = fun.(entry, acc)
{[new_entry|list], acc}
end)
{:lists.reverse(list), acc}
end
@doc """
Returns the maximum value.
Raises `EmptyError` if the collection is empty.
## Examples
iex> Enum.max([1, 2, 3])
3
"""
@spec max(t) :: element | no_return
def max(collection) do
reduce(collection, &Kernel.max(&1, &2))
end
@doc """
Returns the maximum value as calculated by the given function.
Raises `EmptyError` if the collection is empty.
## Examples
iex> Enum.max_by(["a", "aa", "aaa"], fn(x) -> String.length(x) end)
"aaa"
"""
@spec max_by(t, (element -> any)) :: element | no_return
def max_by([h|t], fun) do
reduce(t, {h, fun.(h)}, fn(entry, {_, fun_max} = old) ->
fun_entry = fun.(entry)
if(fun_entry > fun_max, do: {entry, fun_entry}, else: old)
end) |> elem(0)
end
def max_by([], _fun) do
raise Enum.EmptyError
end
def max_by(collection, fun) do
result =
reduce(collection, :first, fn
entry, {_, fun_max} = old ->
fun_entry = fun.(entry)
if(fun_entry > fun_max, do: {entry, fun_entry}, else: old)
entry, :first ->
{entry, fun.(entry)}
end)
case result do
:first -> raise Enum.EmptyError
{entry, _} -> entry
end
end
@doc """
Checks if `value` exists within the `collection`.
Membership is tested with the match (`===`) operator, although
enumerables like ranges may include floats inside the given
range.
## Examples
iex> Enum.member?(1..10, 5)
true
iex> Enum.member?([:a, :b, :c], :d)
false
"""
@spec member?(t, element) :: boolean
def member?(collection, value) when is_list(collection) do
:lists.member(value, collection)
end
def member?(collection, value) do
case Enumerable.member?(collection, value) do
{:ok, value} when is_boolean(value) ->
value
{:error, module} ->
module.reduce(collection, {:cont, false}, fn
v, _ when v === value -> {:halt, true}
_, _ -> {:cont, false}
end) |> elem(1)
end
end
@doc """
Returns the minimum value.
Raises `EmptyError` if the collection is empty.
## Examples
iex> Enum.min([1, 2, 3])
1
"""
@spec min(t) :: element | no_return
def min(collection) do
reduce(collection, &Kernel.min(&1, &2))
end
@doc """
Returns the minimum value as calculated by the given function.
Raises `EmptyError` if the collection is empty.
## Examples
iex> Enum.min_by(["a", "aa", "aaa"], fn(x) -> String.length(x) end)
"a"
"""
@spec min_by(t, (element -> any)) :: element | no_return
def min_by([h|t], fun) do
reduce(t, {h, fun.(h)}, fn(entry, {_, fun_min} = old) ->
fun_entry = fun.(entry)
if(fun_entry < fun_min, do: {entry, fun_entry}, else: old)
end) |> elem(0)
end
def min_by([], _fun) do
raise Enum.EmptyError
end
def min_by(collection, fun) do
result =
reduce(collection, :first, fn
entry, {_, fun_min} = old ->
fun_entry = fun.(entry)
if(fun_entry < fun_min, do: {entry, fun_entry}, else: old)
entry, :first ->
{entry, fun.(entry)}
end)
case result do
:first -> raise Enum.EmptyError
{entry, _} -> entry
end
end
@doc """
Returns the sum of all values.
Raises `ArithmeticError` if collection contains a non-numeric value.
## Examples
iex> Enum.sum([1, 2, 3])
6
"""
@spec sum(t) :: number
def sum(collection) do
reduce(collection, 0, &+/2)
end
@doc """
Partitions `collection` into two collections, where the first one contains elements
for which `fun` returns a truthy value, and the second one -- for which `fun`
returns `false` or `nil`.
## Examples
iex> Enum.partition([1, 2, 3], fn(x) -> rem(x, 2) == 0 end)
{[2], [1,3]}
"""
@spec partition(t, (element -> any)) :: {list, list}
def partition(collection, fun) do
{acc1, acc2} =
reduce(collection, {[], []}, fn(entry, {acc1, acc2}) ->
if fun.(entry) do
{[entry|acc1], acc2}
else
{acc1, [entry|acc2]}
end
end)
{:lists.reverse(acc1), :lists.reverse(acc2)}
end
@doc """
Splits `collection` into groups based on `fun`.
The result is a dict (by default a map) where each key is
a group and each value is a list of elements from `collection`
for which `fun` returned that group. Ordering is not necessarily
preserved.
## Examples
iex> Enum.group_by(~w{ant buffalo cat dingo}, &String.length/1)
%{3 => ["cat", "ant"], 7 => ["buffalo"], 5 => ["dingo"]}
"""
@spec group_by(t, dict, (element -> any)) :: dict when dict: Dict.t
def group_by(collection, dict \\ %{}, fun) do
reduce(collection, dict, fn(entry, categories) ->
Dict.update(categories, fun.(entry), [entry], &[entry|&1])
end)
end
@doc """
Invokes `fun` for each element in the collection passing that element and the
accumulator `acc` as arguments. `fun`'s return value is stored in `acc`.
Returns the accumulator.
## Examples
iex> Enum.reduce([1, 2, 3], 0, fn(x, acc) -> x + acc end)
6
"""
@spec reduce(t, any, (element, any -> any)) :: any
def reduce(collection, acc, fun) when is_list(collection) do
:lists.foldl(fun, acc, collection)
end
def reduce(collection, acc, fun) do
Enumerable.reduce(collection, {:cont, acc},
fn x, acc -> {:cont, fun.(x, acc)} end) |> elem(1)
end
@doc """
Invokes `fun` for each element in the collection passing that element and the
accumulator `acc` as arguments. `fun`'s return value is stored in `acc`.
The first element of the collection is used as the initial value of `acc`.
Returns the accumulator.
## Examples
iex> Enum.reduce([1, 2, 3, 4], fn(x, acc) -> x * acc end)
24
"""
@spec reduce(t, (element, any -> any)) :: any
def reduce([h|t], fun) do
reduce(t, h, fun)
end
def reduce([], _fun) do
raise Enum.EmptyError
end
def reduce(collection, fun) do
result =
Enumerable.reduce(collection, {:cont, :first}, fn
x, :first ->
{:cont, {:acc, x}}
x, {:acc, acc} ->
{:cont, {:acc, fun.(x, acc)}}
end) |> elem(1)
case result do
:first -> raise Enum.EmptyError
{:acc, acc} -> acc
end
end
@doc """
Returns elements of collection for which `fun` returns `false`.
## Examples
iex> Enum.reject([1, 2, 3], fn(x) -> rem(x, 2) == 0 end)
[1, 3]
"""
@spec reject(t, (element -> as_boolean(term))) :: list
def reject(collection, fun) when is_list(collection) do
for item <- collection, !fun.(item), do: item
end
def reject(collection, fun) do
Enumerable.reduce(collection, {:cont, []}, R.reject(fun)) |> elem(1) |> :lists.reverse
end
@doc """
Reverses the collection.
## Examples
iex> Enum.reverse([1, 2, 3])
[3, 2, 1]
"""
@spec reverse(t) :: list
def reverse(collection) when is_list(collection) do
:lists.reverse(collection)
end
def reverse(collection) do
reverse(collection, [])
end
@doc """
Reverses the collection and appends the tail.
This is an optimization for
`Enum.concat(Enum.reverse(collection), tail)`.
## Examples
iex> Enum.reverse([1, 2, 3], [4, 5, 6])
[3, 2, 1, 4, 5, 6]
"""
@spec reverse(t, t) :: list
def reverse(collection, tail) when is_list(collection) and is_list(tail) do
:lists.reverse(collection, tail)
end
def reverse(collection, tail) do
reduce(collection, to_list(tail), fn(entry, acc) ->
[entry|acc]
end)
end
@doc """
Applies the given function to each element in the collection,
storing the result in a list and passing it as the accumulator
for the next computation.
## Examples
iex> Enum.scan(1..5, &(&1 + &2))
[1,3,6,10,15]
"""
@spec scan(t, (element, any -> any)) :: list
def scan(enum, fun) do
{_, {res, _}} =
Enumerable.reduce(enum, {:cont, {[], :first}}, R.scan_2(fun))
:lists.reverse(res)
end
@doc """
Applies the given function to each element in the collection,
storing the result in a list and passing it as the accumulator
for the next computation. Uses the given `acc` as the starting value.
## Examples
iex> Enum.scan(1..5, 0, &(&1 + &2))
[1,3,6,10,15]
"""
@spec scan(t, any, (element, any -> any)) :: list
def scan(enum, acc, fun) do
{_, {res, _}} =
Enumerable.reduce(enum, {:cont, {[], acc}}, R.scan_3(fun))
:lists.reverse(res)
end
@doc """
Returns a list of collection elements shuffled.
Notice that you need to explicitly call `:random.seed/1` and
set a seed value for the random algorithm. Otherwise, the
default seed will be set which will always return the same
result. For example, one could do the following to set a seed
dynamically:
:random.seed(:erlang.now)
## Examples
iex> Enum.shuffle([1, 2, 3])
[3, 2, 1]
iex> Enum.shuffle([1, 2, 3])
[3, 1, 2]
"""
@spec shuffle(t) :: list
def shuffle(collection) do
randomized = reduce(collection, [], fn x, acc ->
[{:random.uniform, x}|acc]
end)
unwrap(:lists.keysort(1, randomized), [])
end
@doc """
Returns a subset list of the given collection. Drops elements
until element position `start`, then takes `count` elements.
## Examples
iex> Enum.slice(1..100, 5, 10)
[6, 7, 8, 9, 10, 11, 12, 13, 14, 15]
"""
@spec slice(t, integer, non_neg_integer) :: list
def slice(coll, start, count) when start < 0 do
{list, new_start} = enumerate_and_count(coll, start)
if new_start >= 0, do: slice(list, new_start, count)
end
def slice(coll, start, count) when is_list(coll) and start >= 0 and count > 0 do
do_slice(coll, start, count)
end
def slice(coll, start, count) when start >= 0 and count > 0 do
{start, _, list} = Enumerable.reduce(coll, {:cont, {start, count, []}}, fn
_entry, {start, count, _list} when start > 0 ->
{:cont, {start-1, count, []}}
entry, {start, count, list} when count > 1 ->
{:cont, {start, count-1, [entry|list]}}
entry, {start, count, list} ->
{:halt, {start, count, [entry|list]}}
end) |> elem(1)
if start <= 0, do: :lists.reverse(list)
end
def slice(coll, start, 0) do
res =
Enumerable.reduce(coll, {:cont, start}, fn _, start ->
if start > 0, do: {:cont, start-1}, else: {:halt, []}
end) |> elem(1)
if is_list(res), do: res
end
@doc """
Returns a subset list of the given collection. Drops elements
until element position `range.first`, then takes elements until element
position `range.last` (inclusive).
Positions are calculated by adding the number of items in the collection to
negative positions (so position -3 in a collection with count 5 becomes
position 2).
The first position (after adding count to negative positions) must be smaller
or equal to the last position.
## Examples
iex> Enum.slice(1..100, 5..10)
[6, 7, 8, 9, 10, 11]
"""
@spec slice(t, Range.t) :: list
def slice(coll, first..last) when first >= 0 and last >= 0 do
# Simple case, which works on infinite collections
if last - first >= 0 do
slice(coll, first, last - first + 1)
end
end
def slice(coll, first..last) do
{list, count} = enumerate_and_count(coll, 0)
corr_first = if first >= 0, do: first, else: first + count
corr_last = if last >= 0, do: last, else: last + count
length = corr_last - corr_first + 1
if corr_first >= 0 and length > 0 do
slice(list, corr_first, length)
end
end
@doc """
Sorts the collection according to Elixir's term ordering.
Uses the merge sort algorithm.
## Examples
iex> Enum.sort([3, 2, 1])
[1, 2, 3]
"""
@spec sort(t) :: list
def sort(collection) when is_list(collection) do
:lists.sort(collection)
end
def sort(collection) do
sort(collection, &(&1 <= &2))
end
@doc """
Sorts the collection by the given function.
This function uses the merge sort algorithm. The given function
must return false if the first argument is less than right one.
## Examples
iex> Enum.sort([1, 2, 3], &(&1 > &2))
[3, 2, 1]
The sorting algorithm will be stable as long as the given function
returns true for values considered equal:
iex> Enum.sort ["some", "kind", "of", "monster"], &(byte_size(&1) <= byte_size(&2))
["of", "some", "kind", "monster"]
If the function does not return true, the sorting is not stable and
the order of equal terms may be shuffled:
iex> Enum.sort ["some", "kind", "of", "monster"], &(byte_size(&1) < byte_size(&2))
["of", "kind", "some", "monster"]
"""
@spec sort(t, (element, element -> boolean)) :: list
def sort(collection, fun) when is_list(collection) do
:lists.sort(fun, collection)
end
def sort(collection, fun) do
reduce(collection, [], &sort_reducer(&1, &2, fun)) |> sort_terminator(fun)
end
@doc """
Splits the enumerable into two collections, leaving `count`
elements in the first one. If `count` is a negative number,
it starts counting from the back to the beginning of the
collection.
Be aware that a negative `count` implies the collection
will be enumerated twice: once to calculate the position, and
a second time to do the actual splitting.
## Examples
iex> Enum.split([1, 2, 3], 2)
{[1,2], [3]}
iex> Enum.split([1, 2, 3], 10)
{[1,2,3], []}
iex> Enum.split([1, 2, 3], 0)
{[], [1,2,3]}
iex> Enum.split([1, 2, 3], -1)
{[1,2], [3]}
iex> Enum.split([1, 2, 3], -5)
{[], [1,2,3]}
"""
@spec split(t, integer) :: {list, list}
def split(collection, count) when is_list(collection) and count >= 0 do
do_split(collection, count, [])
end
def split(collection, count) when count >= 0 do
{_, list1, list2} =
reduce(collection, {count, [], []}, fn(entry, {counter, acc1, acc2}) ->
if counter > 0 do
{counter - 1, [entry|acc1], acc2}
else
{counter, acc1, [entry|acc2]}
end
end)
{:lists.reverse(list1), :lists.reverse(list2)}
end
def split(collection, count) when count < 0 do
do_split_reverse(reverse(collection), abs(count), [])
end
@doc """
Splits `collection` in two while `fun` returns `true`.
## Examples
iex> Enum.split_while([1, 2, 3, 4], fn(x) -> x < 3 end)
{[1, 2], [3, 4]}
"""
@spec split_while(t, (element -> as_boolean(term))) :: {list, list}
def split_while(collection, fun) when is_list(collection) do
do_split_while(collection, fun, [])
end
def split_while(collection, fun) do
{list1, list2} =
reduce(collection, {[], []}, fn
entry, {acc1, []} ->
if(fun.(entry), do: {[entry|acc1], []}, else: {acc1, [entry]})
entry, {acc1, acc2} ->
{acc1, [entry|acc2]}
end)
{:lists.reverse(list1), :lists.reverse(list2)}
end
@doc """
Takes the first `count` items from the collection.
If a negative `count` is given, the last `count` values will
be taken. For such, the collection is fully enumerated keeping up
to `2 * count` elements in memory. Once the end of the collection is
reached, the last `count` elements are returned.
## Examples
iex> Enum.take([1, 2, 3], 2)
[1,2]
iex> Enum.take([1, 2, 3], 10)
[1,2,3]
iex> Enum.take([1, 2, 3], 0)
[]
iex> Enum.take([1, 2, 3], -1)
[3]
"""
@spec take(t, integer) :: list
def take(_collection, 0) do
[]
end
def take(collection, count) when is_list(collection) and count > 0 do
do_take(collection, count)
end
def take(collection, count) when count > 0 do
{_, {res, _}} =
Enumerable.reduce(collection, {:cont, {[], count}}, fn(entry, {list, count}) ->
if count > 1 do
{:cont, {[entry|list], count - 1}}
else
{:halt, {[entry|list], count}}
end
end)
:lists.reverse(res)
end
def take(collection, count) when count < 0 do
Stream.take(collection, count).({:cont, []}, &{:cont, [&1|&2]})
|> elem(1) |> :lists.reverse
end
@doc """
Returns a collection of every `nth` item in the collection,
starting with the first element.
## Examples
iex> Enum.take_every(1..10, 2)
[1, 3, 5, 7, 9]
"""
@spec take_every(t, integer) :: list
def take_every(_collection, 0), do: []
def take_every(collection, nth) do
{_, {res, _}} =
Enumerable.reduce(collection, {:cont, {[], :first}}, R.take_every(nth))
:lists.reverse(res)
end
@doc """
Takes the items at the beginning of `collection` while `fun` returns `true`.
## Examples
iex> Enum.take_while([1, 2, 3], fn(x) -> x < 3 end)
[1, 2]
"""
@spec take_while(t, (element -> as_boolean(term))) :: list
def take_while(collection, fun) when is_list(collection) do
do_take_while(collection, fun)
end
def take_while(collection, fun) do
Enumerable.reduce(collection, {:cont, []}, R.take_while(fun))
|> elem(1) |> :lists.reverse
end
@doc """
Convert `collection` to a list.
## Examples
iex> Enum.to_list(1 .. 3)
[1, 2, 3]
"""
@spec to_list(t) :: [term]
def to_list(collection) when is_list(collection) do
collection
end
def to_list(collection) do
reverse(collection) |> :lists.reverse
end
@doc """
Traverses the given enumerable keeping its shape.
It also expects the enumerable to implement the `Collectable` protocol.
## Examples
iex> Enum.traverse(%{a: 1, b: 2}, fn {k, v} -> {k, v * 2} end)
%{a: 2, b: 4}
"""
@spec traverse(Enumerable.t, (term -> term)) :: Collectable.t
def traverse(collection, transform) when is_list(collection) do
:lists.map(transform, collection)
end
def traverse(collection, transform) do
into(collection, Collectable.empty(collection), transform)
end
@doc """
Enumerates the collection, removing all duplicated items.
## Examples
iex> Enum.uniq([1, 2, 3, 2, 1])
[1, 2, 3]
iex> Enum.uniq([{1, :x}, {2, :y}, {1, :z}], fn {x, _} -> x end)
[{1,:x}, {2,:y}]
"""
@spec uniq(t) :: list
@spec uniq(t, (element -> term)) :: list
def uniq(collection, fun \\ fn x -> x end)
def uniq(collection, fun) when is_list(collection) do
do_uniq(collection, [], fun)
end
def uniq(collection, fun) do
{_, {list, _}} =
Enumerable.reduce(collection, {:cont, {[], []}}, R.uniq(fun))
:lists.reverse(list)
end
@doc """
Zips corresponding elements from two collections into one list
of tuples.
The zipping finishes as soon as any enumerable completes.
## Examples
iex> Enum.zip([1, 2, 3], [:a, :b, :c])
[{1,:a},{2,:b},{3,:c}]
iex> Enum.zip([1,2,3,4,5], [:a, :b, :c])
[{1,:a},{2,:b},{3,:c}]
"""
@spec zip(t, t) :: [{any, any}]
def zip(coll1, coll2) when is_list(coll1) and is_list(coll2) do
do_zip(coll1, coll2)
end
def zip(coll1, coll2) do
Stream.zip(coll1, coll2).({:cont, []}, &{:cont, [&1|&2]}) |> elem(1) |> :lists.reverse
end
@doc """
Returns the collection with each element wrapped in a tuple
alongside its index.
## Examples
iex> Enum.with_index [1,2,3]
[{1,0},{2,1},{3,2}]
"""
@spec with_index(t) :: list({element, non_neg_integer})
def with_index(collection) do
map_reduce(collection, 0, fn x, acc ->
{{x, acc}, acc + 1}
end) |> elem(0)
end
## Helpers
@compile {:inline, to_string: 2}
defp enumerate_and_count(collection, count) when is_list(collection) do
{collection, length(collection) - abs(count)}
end
defp enumerate_and_count(collection, count) do
map_reduce(collection, -abs(count), fn(x, acc) -> {x, acc + 1} end)
end
defp to_string(mapper, entry) do
case mapper.(entry) do
x when is_binary(x) -> x
o -> String.Chars.to_string(o)
end
end
## Implementations
## all?
defp do_all?([h|t], fun) do
if fun.(h) do
do_all?(t, fun)
else
false
end
end
defp do_all?([], _) do
true
end
## any?
defp do_any?([h|t], fun) do
if fun.(h) do
true
else
do_any?(t, fun)
end
end
defp do_any?([], _) do
false
end
## fetch
defp do_fetch([h|_], 0), do: {:ok, h}
defp do_fetch([_|t], n), do: do_fetch(t, n - 1)
defp do_fetch([], _), do: :error
## drop
defp do_drop([_|t], counter) when counter > 0 do
do_drop(t, counter - 1)
end
defp do_drop(list, 0) do
list
end
defp do_drop([], _) do
[]
end
## drop_while
defp do_drop_while([h|t], fun) do
if fun.(h) do
do_drop_while(t, fun)
else
[h|t]
end
end
defp do_drop_while([], _) do
[]
end
## find
defp do_find([h|t], ifnone, fun) do
if fun.(h) do
h
else
do_find(t, ifnone, fun)
end
end
defp do_find([], ifnone, _) do
ifnone
end
## find_index
defp do_find_index([h|t], counter, fun) do
if fun.(h) do
counter
else
do_find_index(t, counter + 1, fun)
end
end
defp do_find_index([], _, _) do
nil
end
## find_value
defp do_find_value([h|t], ifnone, fun) do
fun.(h) || do_find_value(t, ifnone, fun)
end
defp do_find_value([], ifnone, _) do
ifnone
end
## shuffle
defp unwrap([{_, h} | collection], t) do
unwrap(collection, [h|t])
end
defp unwrap([], t), do: t
## sort
defp sort_reducer(entry, {:split, y, x, r, rs, bool}, fun) do
cond do
fun.(y, entry) == bool ->
{:split, entry, y, [x|r], rs, bool}
fun.(x, entry) == bool ->
{:split, y, entry, [x|r], rs, bool}
r == [] ->
{:split, y, x, [entry], rs, bool}
true ->
{:pivot, y, x, r, rs, entry, bool}
end
end
defp sort_reducer(entry, {:pivot, y, x, r, rs, s, bool}, fun) do
cond do
fun.(y, entry) == bool ->
{:pivot, entry, y, [x | r], rs, s, bool}
fun.(x, entry) == bool ->
{:pivot, y, entry, [x | r], rs, s, bool}
fun.(s, entry) == bool ->
{:split, entry, s, [], [[y, x | r] | rs], bool}
true ->
{:split, s, entry, [], [[y, x | r] | rs], bool}
end
end
defp sort_reducer(entry, [x], fun) do
{:split, entry, x, [], [], fun.(x, entry)}
end
defp sort_reducer(entry, acc, _fun) do
[entry|acc]
end
defp sort_terminator({:split, y, x, r, rs, bool}, fun) do
sort_merge([[y, x | r] | rs], fun, bool)
end
defp sort_terminator({:pivot, y, x, r, rs, s, bool}, fun) do
sort_merge([[s], [y, x | r] | rs], fun, bool)
end
defp sort_terminator(acc, _fun) do
acc
end
defp sort_merge(list, fun, true), do:
reverse_sort_merge(list, [], fun, true)
defp sort_merge(list, fun, false), do:
sort_merge(list, [], fun, false)
defp sort_merge([t1, [h2 | t2] | l], acc, fun, true), do:
sort_merge(l, [sort_merge_1(t1, h2, t2, [], fun, false) | acc], fun, true)
defp sort_merge([[h2 | t2], t1 | l], acc, fun, false), do:
sort_merge(l, [sort_merge_1(t1, h2, t2, [], fun, false) | acc], fun, false)
defp sort_merge([l], [], _fun, _bool), do: l
defp sort_merge([l], acc, fun, bool), do:
reverse_sort_merge([:lists.reverse(l, []) | acc], [], fun, bool)
defp sort_merge([], acc, fun, bool), do:
reverse_sort_merge(acc, [], fun, bool)
defp reverse_sort_merge([[h2 | t2], t1 | l], acc, fun, true), do:
reverse_sort_merge(l, [sort_merge_1(t1, h2, t2, [], fun, true) | acc], fun, true)
defp reverse_sort_merge([t1, [h2 | t2] | l], acc, fun, false), do:
reverse_sort_merge(l, [sort_merge_1(t1, h2, t2, [], fun, true) | acc], fun, false)
defp reverse_sort_merge([l], acc, fun, bool), do:
sort_merge([:lists.reverse(l, []) | acc], [], fun, bool)
defp reverse_sort_merge([], acc, fun, bool), do:
sort_merge(acc, [], fun, bool)
defp sort_merge_1([h1 | t1], h2, t2, m, fun, bool) do
if fun.(h1, h2) == bool do
sort_merge_2(h1, t1, t2, [h2 | m], fun, bool)
else
sort_merge_1(t1, h2, t2, [h1 | m], fun, bool)
end
end
defp sort_merge_1([], h2, t2, m, _fun, _bool), do:
:lists.reverse(t2, [h2 | m])
defp sort_merge_2(h1, t1, [h2 | t2], m, fun, bool) do
if fun.(h1, h2) == bool do
sort_merge_2(h1, t1, t2, [h2 | m], fun, bool)
else
sort_merge_1(t1, h2, t2, [h1 | m], fun, bool)
end
end
defp sort_merge_2(h1, t1, [], m, _fun, _bool), do:
:lists.reverse(t1, [h1 | m])
## split
defp do_split([h|t], counter, acc) when counter > 0 do
do_split(t, counter - 1, [h|acc])
end
defp do_split(list, 0, acc) do
{:lists.reverse(acc), list}
end
defp do_split([], _, acc) do
{:lists.reverse(acc), []}
end
defp do_split_reverse([h|t], counter, acc) when counter > 0 do
do_split_reverse(t, counter - 1, [h|acc])
end
defp do_split_reverse(list, 0, acc) do
{:lists.reverse(list), acc}
end
defp do_split_reverse([], _, acc) do
{[], acc}
end
## split_while
defp do_split_while([h|t], fun, acc) do
if fun.(h) do
do_split_while(t, fun, [h|acc])
else
{:lists.reverse(acc), [h|t]}
end
end
defp do_split_while([], _, acc) do
{:lists.reverse(acc), []}
end
## take
defp do_take([h|t], counter) when counter > 0 do
[h|do_take(t, counter - 1)]
end
defp do_take(_list, 0) do
[]
end
defp do_take([], _) do
[]
end
## take_while
defp do_take_while([h|t], fun) do
if fun.(h) do
[h|do_take_while(t, fun)]
else
[]
end
end
defp do_take_while([], _) do
[]
end
## uniq
defp do_uniq([h|t], acc, fun) do
fun_h = fun.(h)
case :lists.member(fun_h, acc) do
true -> do_uniq(t, acc, fun)
false -> [h|do_uniq(t, [fun_h|acc], fun)]
end
end
defp do_uniq([], _acc, _fun) do
[]
end
## zip
defp do_zip([h1|next1], [h2|next2]) do
[{h1, h2}|do_zip(next1, next2)]
end
defp do_zip(_, []), do: []
defp do_zip([], _), do: []
## slice
defp do_slice([], start, _count) do
if start == 0, do: []
end
defp do_slice(list, start, 0) do
if start < length(list), do: []
end
defp do_slice([h|t], 0, count) do
[h|do_slice(t, 0, count-1)]
end
defp do_slice([_|t], start, count) do
do_slice(t, start-1, count)
end
end
defimpl Enumerable, for: List do
def reduce(_, {:halt, acc}, _fun), do: {:halted, acc}
def reduce(list, {:suspend, acc}, fun), do: {:suspended, acc, &reduce(list, &1, fun)}
def reduce([], {:cont, acc}, _fun), do: {:done, acc}
def reduce([h|t], {:cont, acc}, fun), do: reduce(t, fun.(h, acc), fun)
def member?(_list, _value),
do: {:error, __MODULE__}
def count(_list),
do: {:error, __MODULE__}
end
defimpl Enumerable, for: Map do
def reduce(map, acc, fun) do
do_reduce(:maps.to_list(map), acc, fun)
end
defp do_reduce(_, {:halt, acc}, _fun), do: {:halted, acc}
defp do_reduce(list, {:suspend, acc}, fun), do: {:suspended, acc, &do_reduce(list, &1, fun)}
defp do_reduce([], {:cont, acc}, _fun), do: {:done, acc}
defp do_reduce([h|t], {:cont, acc}, fun), do: do_reduce(t, fun.(h, acc), fun)
def member?(map, {key, value}) do
{:ok, match?({:ok, ^value}, :maps.find(key, map))}
end
def member?(_map, _other) do
{:ok, false}
end
def count(map) do
{:ok, map_size(map)}
end
end
defimpl Enumerable, for: Function do
def reduce(function, acc, fun) when is_function(function, 2),
do: function.(acc, fun)
def member?(_function, _value),
do: {:error, __MODULE__}
def count(_function),
do: {:error, __MODULE__}
end