2879 lines
73 KiB
Elixir
2879 lines
73 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 `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 consuming 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-supplied function is wrapped into a `t:reducer/0` function.
|
||
The `t:reducer/0` function must return a tagged tuple after each step,
|
||
as described in the `t:acc/0` type.
|
||
|
||
The reason the accumulator requires a tagged tuple is to allow the
|
||
`t:reducer/0` function to communicate the end of enumeration to the underlying
|
||
enumerable, allowing any open resources 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 `t: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 `t:result/0`
|
||
type documentation for more information.
|
||
|
||
In case a `t:reducer/0` 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 enumerable 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 `t:reducer/0` function.
|
||
|
||
In case a `t:reducer/0` 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 enumerable into an element.
|
||
|
||
Most of the operations in `Enum` are implemented in terms of reduce.
|
||
This function should apply the given `t:reducer/0` function to each
|
||
item in the enumerable 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(enumerable, acc, fun)
|
||
|
||
@doc """
|
||
Checks if an element exists within the enumerable.
|
||
|
||
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?(enumerable, element)
|
||
|
||
@doc """
|
||
Retrieves the enumerable'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(enumerable)
|
||
end
|
||
|
||
defmodule Enum do
|
||
import Kernel, except: [max: 2, min: 2]
|
||
|
||
@moduledoc """
|
||
Provides a set of algorithms that enumerate over enumerables according
|
||
to the `Enumerable` protocol.
|
||
|
||
iex> Enum.map([1, 2, 3], fn(x) -> x * 2 end)
|
||
[2, 4, 6]
|
||
|
||
Some particular types, like maps, yield a specific format on enumeration.
|
||
For example, the argument is always a `{key, value}` tuple for maps:
|
||
|
||
iex> map = %{a: 1, b: 2}
|
||
iex> Enum.map(map, 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 enumerable. The `Stream` module
|
||
allows lazy enumeration of enumerables and provides infinite streams.
|
||
|
||
Since the majority of the functions in `Enum` enumerate the whole
|
||
enumerable 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 skip(acc) do
|
||
acc
|
||
end
|
||
|
||
defmacrop next(_, entry, acc) do
|
||
quote do: [unquote(entry) | unquote(acc)]
|
||
end
|
||
|
||
defmacrop acc(h, n, _) do
|
||
quote do: {unquote(h), unquote(n)}
|
||
end
|
||
|
||
defmacrop next_with_acc(f, entry, h, n, _) do
|
||
quote do
|
||
{[unquote(entry) | unquote(h)], unquote(n)}
|
||
end
|
||
end
|
||
|
||
@doc """
|
||
Invokes the given `fun` for each item in the enumerable.
|
||
It stops the iteration at the first invocation that returns `false` or `nil`.
|
||
It returns `false` if at least one invocation returns `false` or `nil`.
|
||
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 enumerable are truthy values.
|
||
|
||
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?(enumerable, fun \\ fn(x) -> x end)
|
||
|
||
def all?(enumerable, fun) when is_list(enumerable) do
|
||
do_all?(enumerable, fun)
|
||
end
|
||
|
||
def all?(enumerable, fun) do
|
||
Enumerable.reduce(enumerable, {: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 enumerable.
|
||
It stops the iteration at the first invocation that returns a truthy value.
|
||
Returns `true` if at least one invocation returns a truthy value.
|
||
Otherwise returns `false`.
|
||
|
||
## 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 enumerable is a truthy value.
|
||
|
||
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?(enumerable, fun \\ fn(x) -> x end)
|
||
|
||
def any?(enumerable, fun) when is_list(enumerable) do
|
||
do_any?(enumerable, fun)
|
||
end
|
||
|
||
def any?(enumerable, fun) do
|
||
Enumerable.reduce(enumerable, {: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.
|
||
|
||
A negative `index` can be passed, which means the `enumerable` is
|
||
enumerated once and the `index` is counted from the end (e.g.
|
||
`-1` finds the last element).
|
||
|
||
Note this operation takes linear time. In order to access
|
||
the element at index `index`, it will need to traverse `index`
|
||
previous elements.
|
||
|
||
## 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, default) :: element | default
|
||
def at(enumerable, index, default \\ nil) do
|
||
case fetch(enumerable, index) do
|
||
{:ok, h} -> h
|
||
:error -> default
|
||
end
|
||
end
|
||
|
||
@doc """
|
||
Shortcut to `chunk(enumerable, count, count)`.
|
||
"""
|
||
@spec chunk(t, pos_integer) :: [list]
|
||
def chunk(enumerable, count), do: chunk(enumerable, count, count, nil)
|
||
|
||
@doc """
|
||
Returns list of lists containing `count` items each, where
|
||
each new chunk starts `step` elements into the enumerable.
|
||
|
||
`step` is optional and, if not passed, defaults to `count`, i.e.
|
||
chunks do not overlap.
|
||
|
||
If the final chunk does not have `count` elements to fill the chunk,
|
||
elements are taken as necessary from `leftover` if it was passed.
|
||
|
||
If `leftover` is passed and does not have enough elements to fill the
|
||
chunk, then a partial chunk is returned with less than `count`
|
||
elements. If `leftover` is not passed at all or is `nil`, then the
|
||
partial chunk is discarded from the result.
|
||
|
||
If `count` is greater than the number of elements in the enumerable
|
||
and `leftover` is not passed, empty list will be returned.
|
||
|
||
## 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], 3, 3, [])
|
||
[[1, 2, 3], [4]]
|
||
|
||
iex> Enum.chunk([1, 2, 3, 4], 10)
|
||
[]
|
||
|
||
iex> Enum.chunk([1, 2, 3, 4], 10, 10, [])
|
||
[[1, 2, 3, 4]]
|
||
|
||
"""
|
||
@spec chunk(t, pos_integer, pos_integer, t | nil) :: [list]
|
||
def chunk(enumerable, count, step, leftover \\ nil)
|
||
when is_integer(count) and count > 0 and is_integer(step) and step > 0 do
|
||
limit = :erlang.max(count, step)
|
||
|
||
{acc, {buffer, i}} =
|
||
reduce(enumerable, {[], {[], 0}}, R.chunk(count, step, limit))
|
||
|
||
if is_nil(leftover) || i == 0 do
|
||
:lists.reverse(acc)
|
||
else
|
||
buffer = :lists.reverse(buffer, take(leftover, count - i))
|
||
:lists.reverse([buffer | acc])
|
||
end
|
||
end
|
||
|
||
@doc """
|
||
Splits enumerable on every element for which `fun` returns a new
|
||
value.
|
||
|
||
Returns a list of lists.
|
||
|
||
## 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(enumerable, fun) do
|
||
{acc, res} = reduce(enumerable, {[], nil}, R.chunk_by(fun))
|
||
|
||
case res do
|
||
{buffer, _} ->
|
||
:lists.reverse([:lists.reverse(buffer) | acc])
|
||
nil ->
|
||
[]
|
||
end
|
||
end
|
||
|
||
@doc """
|
||
Given an enumerable of enumerables, concatenates 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 size of the enumerable.
|
||
|
||
## Examples
|
||
|
||
iex> Enum.count([1, 2, 3])
|
||
3
|
||
|
||
"""
|
||
@spec count(t) :: non_neg_integer
|
||
def count(enumerable) when is_list(enumerable) do
|
||
:erlang.length(enumerable)
|
||
end
|
||
|
||
def count(enumerable) do
|
||
case Enumerable.count(enumerable) do
|
||
{:ok, value} when is_integer(value) ->
|
||
value
|
||
{:error, module} ->
|
||
module.reduce(enumerable, {:cont, 0}, fn
|
||
_, acc -> {:cont, acc + 1}
|
||
end) |> elem(1)
|
||
end
|
||
end
|
||
|
||
@doc """
|
||
Returns the count of items in the enumerable for which `fun` returns
|
||
a truthy value.
|
||
|
||
## 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(enumerable, fun) do
|
||
Enumerable.reduce(enumerable, {:cont, 0}, fn(entry, acc) ->
|
||
{:cont, if(fun.(entry), do: acc + 1, else: acc)}
|
||
end) |> elem(1)
|
||
end
|
||
|
||
@doc """
|
||
Enumerates the `enumerable`, returning a list where all consecutive
|
||
duplicated elements are collapsed to a single element.
|
||
|
||
Elements are compared using `===`.
|
||
|
||
If you want to remove all duplicated elements, regardless of order,
|
||
see `uniq/1`.
|
||
|
||
## Examples
|
||
|
||
iex> Enum.dedup([1, 2, 3, 3, 2, 1])
|
||
[1, 2, 3, 2, 1]
|
||
|
||
iex> Enum.dedup([1, 1, 2, 2.0, :three, :"three"])
|
||
[1, 2, 2.0, :three]
|
||
|
||
"""
|
||
@spec dedup(t) :: list
|
||
def dedup(enumerable) do
|
||
dedup_by(enumerable, fn x -> x end)
|
||
end
|
||
|
||
@doc """
|
||
Enumerates the `enumerable`, returning a list where all consecutive
|
||
duplicated elements are collapsed to a single element.
|
||
|
||
The function `fun` maps every element to a term which is used to
|
||
determine if two elements are duplicates.
|
||
|
||
## Examples
|
||
|
||
iex> Enum.dedup_by([{1, :a}, {2, :b}, {2, :c}, {1, :a}], fn {x, _} -> x end)
|
||
[{1, :a}, {2, :b}, {1, :a}]
|
||
|
||
iex> Enum.dedup_by([5, 1, 2, 3, 2, 1], fn x -> x > 2 end)
|
||
[5, 1, 3, 2]
|
||
|
||
"""
|
||
@spec dedup_by(t, (element -> term)) :: list
|
||
def dedup_by(enumerable, fun) when is_function(fun, 1) do
|
||
{list, _} = reduce(enumerable, {[], []}, R.dedup(fun))
|
||
:lists.reverse(list)
|
||
end
|
||
|
||
@doc """
|
||
Drops the first `n` items from then enumerable.
|
||
|
||
If a negative value `n` is given, the last `n` values will be dropped.
|
||
|
||
The `enumerable` 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(enumerable, n) when is_list(enumerable) and n >= 0 do
|
||
do_drop(enumerable, n)
|
||
end
|
||
|
||
def drop(enumerable, n) when n >= 0 do
|
||
res =
|
||
reduce(enumerable, n, 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(enumerable, n) when n < 0 do
|
||
do_drop(reverse(enumerable), abs(n)) |> :lists.reverse
|
||
end
|
||
|
||
@doc """
|
||
Returns a list of every `nth` item in the enumerable dropped,
|
||
starting with the first element.
|
||
|
||
The first item is always dropped, unless `nth` is 0.
|
||
|
||
The second argument specifying every `nth` item must be a non-negative
|
||
integer, otherwise `FunctionClauseError` will be raised.
|
||
|
||
## Examples
|
||
|
||
iex> Enum.drop_every(1..10, 2)
|
||
[2, 4, 6, 8, 10]
|
||
|
||
iex> Enum.drop_every(1..10, 0)
|
||
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
|
||
|
||
iex> Enum.drop_every([1, 2, 3], 1)
|
||
[]
|
||
|
||
"""
|
||
@spec drop_every(t, non_neg_integer) :: list | no_return
|
||
def drop_every(enumerable, nth)
|
||
|
||
def drop_every(_enumerable, 1), do: []
|
||
def drop_every(enumerable, 0), do: to_list(enumerable)
|
||
def drop_every([], _nth), do: []
|
||
|
||
def drop_every(enumerable, nth) when is_integer(nth) and nth > 0 do
|
||
{res, _} = reduce(enumerable, {[], :first}, R.drop_every(nth))
|
||
:lists.reverse(res)
|
||
end
|
||
|
||
@doc """
|
||
Drops items at the beginning of the enumerable while `fun` returns a
|
||
truthy value.
|
||
|
||
## 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(enumerable, fun) when is_list(enumerable) do
|
||
do_drop_while(enumerable, fun)
|
||
end
|
||
|
||
def drop_while(enumerable, fun) do
|
||
{res, _} = reduce(enumerable, {[], true}, R.drop_while(fun))
|
||
:lists.reverse(res)
|
||
end
|
||
|
||
@doc """
|
||
Invokes the given `fun` for each item in the enumerable.
|
||
|
||
Returns `:ok`.
|
||
|
||
## Examples
|
||
|
||
Enum.each(["some", "example"], fn(x) -> IO.puts x end)
|
||
"some"
|
||
"example"
|
||
#=> :ok
|
||
|
||
"""
|
||
@spec each(t, (element -> any)) :: :ok
|
||
def each(enumerable, fun) when is_list(enumerable) do
|
||
:lists.foreach(fun, enumerable)
|
||
:ok
|
||
end
|
||
|
||
def each(enumerable, fun) do
|
||
reduce(enumerable, nil, fn(entry, _) ->
|
||
fun.(entry)
|
||
nil
|
||
end)
|
||
:ok
|
||
end
|
||
|
||
@doc """
|
||
Determines if the enumerable is empty.
|
||
|
||
Returns `true` if `enumerable` is empty, otherwise `false`.
|
||
|
||
## Examples
|
||
|
||
iex> Enum.empty?([])
|
||
true
|
||
|
||
iex> Enum.empty?([1, 2, 3])
|
||
false
|
||
|
||
"""
|
||
@spec empty?(t) :: boolean
|
||
def empty?(enumerable) when is_list(enumerable) do
|
||
enumerable == []
|
||
end
|
||
|
||
def empty?(enumerable) do
|
||
case Enumerable.count(enumerable) do
|
||
{:ok, value} when is_integer(value) ->
|
||
value == 0
|
||
{:error, module} ->
|
||
module.reduce(enumerable, {:cont, true},
|
||
fn(_, _) -> {:halt, false} end)
|
||
|> elem(1)
|
||
end
|
||
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 `enumerable` is
|
||
enumerated once and the `index` is counted from the end (e.g.
|
||
`-1` fetches the last element).
|
||
|
||
Note this operation takes linear time. In order to access
|
||
the element at index `index`, it will need to traverse `index`
|
||
previous elements.
|
||
|
||
## 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(enumerable, index) when is_list(enumerable)
|
||
and is_integer(index) and index >= 0 do
|
||
do_fetch(enumerable, index)
|
||
end
|
||
|
||
def fetch(enumerable, index) when is_integer(index) and index >= 0 do
|
||
res =
|
||
Enumerable.reduce(enumerable, {:cont, 0}, fn(entry, acc) ->
|
||
if acc == index do
|
||
{:halt, entry}
|
||
else
|
||
{:cont, acc + 1}
|
||
end
|
||
end)
|
||
|
||
case res do
|
||
{:halted, entry} -> {:ok, entry}
|
||
{:done, _} -> :error
|
||
end
|
||
end
|
||
|
||
def fetch(enumerable, index) when is_integer(index) and index < 0 do
|
||
do_fetch(reverse(enumerable), abs(index + 1))
|
||
end
|
||
|
||
@doc """
|
||
Finds the element at the given `index` (zero-based).
|
||
|
||
Raises `OutOfBoundsError` if the given `index` is outside the range of
|
||
the enumerable.
|
||
|
||
Note this operation takes linear time. In order to access the element
|
||
at index `index`, it will need to traverse `index` previous elements.
|
||
|
||
## 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!(enumerable, index) do
|
||
case fetch(enumerable, index) do
|
||
{:ok, h} -> h
|
||
:error -> raise Enum.OutOfBoundsError
|
||
end
|
||
end
|
||
|
||
@doc """
|
||
Filters the enumerable, i.e. returns only those elements
|
||
for which `fun` returns a truthy value.
|
||
|
||
See also `reject/2`.
|
||
|
||
## 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(enumerable, fun) when is_list(enumerable) do
|
||
for item <- enumerable, fun.(item), do: item
|
||
end
|
||
|
||
def filter(enumerable, fun) do
|
||
reduce(enumerable, [], R.filter(fun)) |> :lists.reverse
|
||
end
|
||
|
||
@doc """
|
||
Filters the enumerable and maps its elements 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(enumerable, filter, mapper) when is_list(enumerable) do
|
||
for item <- enumerable, filter.(item), do: mapper.(item)
|
||
end
|
||
|
||
def filter_map(enumerable, filter, mapper) do
|
||
reduce(enumerable, [], R.filter_map(filter, mapper))
|
||
|> :lists.reverse
|
||
end
|
||
|
||
@doc """
|
||
Returns the first item for which `fun` returns a truthy value.
|
||
If no such item is found, returns `default`.
|
||
|
||
## 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, default, (element -> any)) :: element | default
|
||
def find(enumerable, default \\ nil, fun)
|
||
|
||
def find(enumerable, default, fun) when is_list(enumerable) do
|
||
do_find(enumerable, default, fun)
|
||
end
|
||
|
||
def find(enumerable, default, fun) do
|
||
Enumerable.reduce(enumerable, {:cont, default}, fn(entry, default) ->
|
||
if fun.(entry), do: {:halt, entry}, else: {:cont, default}
|
||
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(enumerable, fun) when is_list(enumerable) do
|
||
do_find_index(enumerable, 0, fun)
|
||
end
|
||
|
||
def find_index(enumerable, fun) do
|
||
res =
|
||
Enumerable.reduce(enumerable, {:cont, {:not_found, 0}}, fn(entry, {_status, index}) ->
|
||
if fun.(entry), do: {:halt, {:found, index}}, else: {:cont, {:not_found, index + 1}}
|
||
end)
|
||
|
||
case res do
|
||
{_, {:found, index}} -> index
|
||
{_, {:not_found, _}} -> nil
|
||
end
|
||
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
|
||
|
||
iex> Enum.find_value([1, 2, 3], "no bools!", &is_boolean/1)
|
||
"no bools!"
|
||
|
||
"""
|
||
@spec find_value(t, any, (element -> any)) :: any | :nil
|
||
def find_value(enumerable, default \\ nil, fun)
|
||
|
||
def find_value(enumerable, default, fun) when is_list(enumerable) do
|
||
do_find_value(enumerable, default, fun)
|
||
end
|
||
|
||
def find_value(enumerable, default, fun) do
|
||
Enumerable.reduce(enumerable, {:cont, default}, fn(entry, default) ->
|
||
fun_entry = fun.(entry)
|
||
if fun_entry, do: {:halt, fun_entry}, else: {:cont, default}
|
||
end) |> elem(1)
|
||
end
|
||
|
||
@doc """
|
||
Returns a new enumerable appending the result of invoking `fun` on
|
||
each corresponding item of `enumerable`.
|
||
|
||
The given function must 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]
|
||
|
||
iex> Enum.flat_map([:a, :b, :c], fn(x) -> [[x]] end)
|
||
[[:a], [:b], [:c]]
|
||
|
||
"""
|
||
@spec flat_map(t, (element -> t)) :: list
|
||
def flat_map(enumerable, fun) do
|
||
reduce(enumerable, [], fn(entry, acc) ->
|
||
reduce(fun.(entry), acc, &[&1 | &2])
|
||
end) |> :lists.reverse
|
||
end
|
||
|
||
@doc """
|
||
Maps and reduces an enumerable, flattening the given results (only one level deep).
|
||
|
||
It expects an accumulator and a function that receives each enumerable
|
||
item, and must return a tuple containing a new enumerable (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}
|
||
|
||
iex> Enum.flat_map_reduce(1..5, 0, fn(i, acc) -> {[[i]], acc + i} end)
|
||
{[[1], [2], [3], [4], [5]], 15}
|
||
|
||
"""
|
||
@spec flat_map_reduce(t, acc, fun) :: {[any], any} when
|
||
fun: (element, acc -> {t, acc} | {:halt, acc}),
|
||
acc: any
|
||
def flat_map_reduce(enumerable, acc, fun) do
|
||
{_, {list, acc}} =
|
||
Enumerable.reduce(enumerable, {:cont, {[], acc}},
|
||
fn(entry, {list, acc}) ->
|
||
case fun.(entry, acc) do
|
||
{:halt, acc} ->
|
||
{:halt, {list, acc}}
|
||
{[], acc} ->
|
||
{:cont, {list, acc}}
|
||
{[entry], acc} ->
|
||
{:cont, {[entry | list], acc}}
|
||
{entries, acc} ->
|
||
{:cont, {reduce(entries, list, &[&1 | &2]), acc}}
|
||
end
|
||
end)
|
||
|
||
{:lists.reverse(list), acc}
|
||
end
|
||
|
||
@doc """
|
||
Splits the enumerable into groups based on `fun`.
|
||
|
||
The result is a map where each key is given by `key_fun` and each
|
||
value is a list of elements given by `value_fun`. Ordering is preserved.
|
||
|
||
## Examples
|
||
|
||
iex> Enum.group_by(~w{ant buffalo cat dingo}, &String.length/1)
|
||
%{3 => ["ant", "cat"], 7 => ["buffalo"], 5 => ["dingo"]}
|
||
|
||
iex> Enum.group_by(~w{ant buffalo cat dingo}, &String.length/1, &String.first/1)
|
||
%{3 => ["a", "c"], 7 => ["b"], 5 => ["d"]}
|
||
|
||
"""
|
||
@spec group_by(t, (element -> any), (element -> any)) :: map
|
||
def group_by(enumerable, key_fun, mapper_fun \\ fn x -> x end)
|
||
|
||
def group_by(enumerable, key_fun, value_fun)
|
||
when is_function(key_fun, 1) and is_function(value_fun, 1) do
|
||
reduce(reverse(enumerable), %{}, fn entry, categories ->
|
||
value = value_fun.(entry)
|
||
Map.update(categories, key_fun.(entry), [value], &[value | &1])
|
||
end)
|
||
end
|
||
|
||
# TODO: Remove on 2.0
|
||
def group_by(enumerable, dict, fun) when is_function(fun, 1) do
|
||
IO.warn "Enum.group_by/3 with a map/dictionary as second element is deprecated. " <>
|
||
"A map is used by default and it is no longer required to pass one to this function"
|
||
reduce(reverse(enumerable), dict, fn(entry, categories) ->
|
||
Dict.update(categories, fun.(entry), [entry], &[entry | &1])
|
||
end)
|
||
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(enumerable, element) do
|
||
list =
|
||
reduce(enumerable, [], 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}
|
||
|
||
iex> Enum.into(%{a: 1}, %{b: 2})
|
||
%{a: 1, b: 2}
|
||
|
||
iex> Enum.into([a: 1, a: 2], %{})
|
||
%{a: 2}
|
||
|
||
"""
|
||
@spec into(Enumerable.t, Collectable.t) :: Collectable.t
|
||
def into(enumerable, collectable) when is_list(collectable) do
|
||
collectable ++ to_list(enumerable)
|
||
end
|
||
|
||
def into(%{__struct__: _} = enumerable, collectable) do
|
||
do_into(enumerable, collectable)
|
||
end
|
||
|
||
def into(enumerable, %{__struct__: _} = collectable) do
|
||
do_into(enumerable, collectable)
|
||
end
|
||
|
||
def into(%{} = enumerable, %{} = collectable) do
|
||
Map.merge(collectable, enumerable)
|
||
end
|
||
|
||
def into(enumerable, %{} = collectable) when is_list(enumerable) do
|
||
Map.merge(collectable, :maps.from_list(enumerable))
|
||
end
|
||
|
||
def into(enumerable, %{} = collectable) do
|
||
reduce(enumerable, collectable, fn {k, v}, acc ->
|
||
Map.put(acc, k, v)
|
||
end)
|
||
end
|
||
|
||
def into(enumerable, collectable) do
|
||
do_into(enumerable, collectable)
|
||
end
|
||
|
||
defp do_into(enumerable, collectable) do
|
||
{initial, fun} = Collectable.into(collectable)
|
||
into(enumerable, 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(enumerable, collectable, transform) when is_list(collectable)
|
||
and is_function(transform, 1) do
|
||
collectable ++ map(enumerable, transform)
|
||
end
|
||
|
||
def into(enumerable, collectable, transform)
|
||
when is_function(transform, 1) do
|
||
{initial, fun} = Collectable.into(collectable)
|
||
into(enumerable, initial, fun, fn x, acc ->
|
||
fun.(acc, {:cont, transform.(x)})
|
||
end)
|
||
end
|
||
|
||
defp into(enumerable, initial, fun, callback) do
|
||
try do
|
||
reduce(enumerable, 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 enumerable into a binary using `joiner` as a
|
||
separator.
|
||
|
||
If `joiner` is not passed at all, it defaults to the empty binary.
|
||
|
||
All items in the enumerable 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) :: String.t
|
||
def join(enumerable, joiner \\ "")
|
||
|
||
def join(enumerable, joiner) when is_binary(joiner) do
|
||
reduced = reduce(enumerable, :first, fn
|
||
entry, :first -> enum_to_string(entry)
|
||
entry, acc -> [acc, joiner | enum_to_string(entry)]
|
||
end)
|
||
if reduced == :first do
|
||
""
|
||
else
|
||
IO.iodata_to_binary reduced
|
||
end
|
||
end
|
||
|
||
@doc """
|
||
Returns a list where each item is the result of invoking
|
||
`fun` on each corresponding item of `enumerable`.
|
||
|
||
For maps, 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(enumerable, fun)
|
||
|
||
def map(enumerable, fun) when is_list(enumerable) do
|
||
:lists.map(fun, enumerable)
|
||
end
|
||
|
||
def map(enumerable, fun) do
|
||
reduce(enumerable, [], R.map(fun)) |> :lists.reverse
|
||
end
|
||
|
||
@doc """
|
||
Maps and joins the given enumerable 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 enumerable 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, String.t, (element -> any)) :: String.t
|
||
def map_join(enumerable, joiner \\ "", mapper)
|
||
|
||
def map_join(enumerable, joiner, mapper) when is_binary(joiner) do
|
||
reduced = reduce(enumerable, :first, fn
|
||
entry, :first -> enum_to_string(mapper.(entry))
|
||
entry, acc -> [acc, joiner | enum_to_string(mapper.(entry))]
|
||
end)
|
||
|
||
if reduced == :first do
|
||
""
|
||
else
|
||
IO.iodata_to_binary reduced
|
||
end
|
||
end
|
||
|
||
@doc """
|
||
Invokes the given function to each item in the enumerable to reduce
|
||
it to a single element, while keeping an accumulator.
|
||
|
||
Returns a tuple where the first element is the mapped enumerable and
|
||
the second one is the final accumulator.
|
||
|
||
The function, `fun`, receives two arguments: the first one is the
|
||
element, and the second one is the accumulator. `fun` must return
|
||
a tuple with two elements in the form of `{result, accumulator}`.
|
||
|
||
For maps, 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})) :: {any, any}
|
||
def map_reduce(enumerable, acc, fun) when is_list(enumerable) do
|
||
:lists.mapfoldl(fun, acc, enumerable)
|
||
end
|
||
|
||
def map_reduce(enumerable, acc, fun) do
|
||
{list, acc} = reduce(enumerable, {[], acc},
|
||
fn(entry, {list, acc}) ->
|
||
{new_entry, acc} = fun.(entry, acc)
|
||
{[new_entry | list], acc}
|
||
end)
|
||
{:lists.reverse(list), acc}
|
||
end
|
||
|
||
@doc """
|
||
Returns the maximal element in the enumerable according
|
||
to Erlang's term ordering.
|
||
|
||
If multiple elements are considered maximal, the first one that was found
|
||
is returned.
|
||
|
||
Raises `Enum.EmptyError` if `enumerable` is empty.
|
||
|
||
## Examples
|
||
|
||
iex> Enum.max([1, 2, 3])
|
||
3
|
||
|
||
"""
|
||
@spec max(t) :: element | no_return
|
||
def max(enumerable) do
|
||
reduce(enumerable, &Kernel.max(&1, &2))
|
||
end
|
||
|
||
@doc """
|
||
Returns the maximal element in the enumerable as calculated
|
||
by the given function.
|
||
|
||
If multiple elements are considered maximal, the first one that was found
|
||
is returned.
|
||
|
||
Raises `Enum.EmptyError` if `enumerable` is empty.
|
||
|
||
## Examples
|
||
|
||
iex> Enum.max_by(["a", "aa", "aaa"], fn(x) -> String.length(x) end)
|
||
"aaa"
|
||
|
||
iex> Enum.max_by(["a", "aa", "aaa", "b", "bbb"], &String.length/1)
|
||
"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(enumerable, fun) do
|
||
result =
|
||
reduce(enumerable, :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 `element` exists within the enumerable.
|
||
|
||
Membership is tested with the match (`===`) operator.
|
||
|
||
## Examples
|
||
|
||
iex> Enum.member?(1..10, 5)
|
||
true
|
||
iex> Enum.member?(1..10, 5.0)
|
||
false
|
||
|
||
iex> Enum.member?([1.0, 2.0, 3.0], 2)
|
||
false
|
||
iex> Enum.member?([1.0, 2.0, 3.0], 2.000)
|
||
true
|
||
|
||
iex> Enum.member?([:a, :b, :c], :d)
|
||
false
|
||
|
||
"""
|
||
@spec member?(t, element) :: boolean
|
||
def member?(enumerable, element) when is_list(enumerable) do
|
||
:lists.member(element, enumerable)
|
||
end
|
||
|
||
def member?(enumerable, element) do
|
||
case Enumerable.member?(enumerable, element) do
|
||
{:ok, element} when is_boolean(element) ->
|
||
element
|
||
{:error, module} ->
|
||
module.reduce(enumerable, {:cont, false}, fn
|
||
v, _ when v === element -> {:halt, true}
|
||
_, _ -> {:cont, false}
|
||
end) |> elem(1)
|
||
end
|
||
end
|
||
|
||
@doc """
|
||
Returns the minimal element in the enumerable according
|
||
to Erlang's term ordering.
|
||
|
||
If multiple elements are considered minimal, the first one that was found
|
||
is returned.
|
||
|
||
Raises `Enum.EmptyError` if `enumerable` is empty.
|
||
|
||
## Examples
|
||
|
||
iex> Enum.min([1, 2, 3])
|
||
1
|
||
|
||
"""
|
||
@spec min(t) :: element | no_return
|
||
def min(enumerable) do
|
||
reduce(enumerable, &Kernel.min(&1, &2))
|
||
end
|
||
|
||
@doc """
|
||
Returns the minimal element in the enumerable as calculated
|
||
by the given function.
|
||
|
||
If multiple elements are considered minimal, the first one that was found
|
||
is returned.
|
||
|
||
Raises `Enum.EmptyError` if `enumerable` is empty.
|
||
|
||
## Examples
|
||
|
||
iex> Enum.min_by(["a", "aa", "aaa"], fn(x) -> String.length(x) end)
|
||
"a"
|
||
|
||
iex> Enum.min_by(["a", "aa", "aaa", "b", "bbb"], &String.length/1)
|
||
"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(enumerable, fun) do
|
||
result =
|
||
reduce(enumerable, :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 a tuple with the minimal and the maximal elements in the
|
||
enumerable according to Erlang's term ordering.
|
||
|
||
If multiple elements are considered maximal or minimal, the first one
|
||
that was found is returned.
|
||
|
||
Raises `Enum.EmptyError` if `enumerable` is empty.
|
||
|
||
## Examples
|
||
|
||
iex> Enum.min_max([2, 3, 1])
|
||
{1, 3}
|
||
|
||
"""
|
||
@spec min_max(t) :: {element, element} | no_return
|
||
def min_max(enumerable) do
|
||
result =
|
||
Enum.reduce(enumerable, :first, fn
|
||
entry, {min_value, max_value} ->
|
||
{Kernel.min(entry, min_value), Kernel.max(entry, max_value)}
|
||
entry, :first ->
|
||
{entry, entry}
|
||
end)
|
||
|
||
case result do
|
||
:first -> raise Enum.EmptyError
|
||
result -> result
|
||
end
|
||
end
|
||
|
||
@doc """
|
||
Returns a tuple with the minimal and the maximal elements in the
|
||
enumerable as calculated by the given function.
|
||
|
||
If multiple elements are considered maximal or minimal, the first one
|
||
that was found is returned.
|
||
|
||
Raises `Enum.EmptyError` if `enumerable` is empty.
|
||
|
||
## Examples
|
||
|
||
iex> Enum.min_max_by(["aaa", "bb", "c"], fn(x) -> String.length(x) end)
|
||
{"c", "aaa"}
|
||
|
||
iex> Enum.min_max_by(["aaa", "a", "bb", "c", "ccc"], &String.length/1)
|
||
{"a", "aaa"}
|
||
|
||
"""
|
||
@spec min_max_by(t, (element -> any)) :: {element, element} | no_return
|
||
def min_max_by(enumerable, fun) do
|
||
result =
|
||
Enum.reduce(enumerable, :first, fn
|
||
entry, {{_, fun_min} = acc_min, {_, fun_max} = acc_max} ->
|
||
fun_entry = fun.(entry)
|
||
acc_min = if fun_entry < fun_min, do: {entry, fun_entry}, else: acc_min
|
||
acc_max = if fun_entry > fun_max, do: {entry, fun_entry}, else: acc_max
|
||
{acc_min, acc_max}
|
||
entry, :first ->
|
||
fun_entry = fun.(entry)
|
||
{{entry, fun_entry}, {entry, fun_entry}}
|
||
end)
|
||
|
||
case result do
|
||
:first ->
|
||
raise Enum.EmptyError
|
||
{{min_entry, _}, {max_entry, _}} ->
|
||
{min_entry, max_entry}
|
||
end
|
||
end
|
||
|
||
@doc """
|
||
Partitions `enumerable` into two lists, 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(enumerable, fun) do
|
||
{acc1, acc2} =
|
||
reduce(enumerable, {[], []}, 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 """
|
||
Returns a random element of an enumerable.
|
||
|
||
Raises `Enum.EmptyError` if `enumerable` is empty.
|
||
|
||
This function uses Erlang's `:rand` module to calculate
|
||
the random value. Check its documentation for setting a
|
||
different random algorithm or a different seed.
|
||
|
||
The implementation is based on the
|
||
[reservoir sampling](https://en.wikipedia.org/wiki/Reservoir_sampling#Relation_to_Fisher-Yates_shuffle)
|
||
algorithm.
|
||
It assumes that the sample being returned can fit into memory;
|
||
the input `enumerable` doesn't have to, as it is traversed just once.
|
||
|
||
## Examples
|
||
|
||
# Although not necessary, let's seed the random algorithm
|
||
iex> :rand.seed(:exsplus, {1, 2, 3})
|
||
iex> Enum.random([1, 2, 3])
|
||
2
|
||
iex> Enum.random([1, 2, 3])
|
||
1
|
||
|
||
"""
|
||
@spec random(t) :: element | no_return
|
||
def random(enumerable) do
|
||
case take_random(enumerable, 1) do
|
||
[] -> raise Enum.EmptyError
|
||
[e] -> e
|
||
end
|
||
end
|
||
|
||
@doc """
|
||
Invokes `fun` for each element in the `enumerable`, passing that
|
||
element and the accumulator as arguments. `fun`'s return value
|
||
is stored in the accumulator.
|
||
|
||
The first element of the enumerable is used as the initial value of
|
||
the accumulator.
|
||
If you wish to use another value for the accumulator, use
|
||
`Enumerable.reduce/3`.
|
||
This function won't call the specified function for enumerables that
|
||
are one-element long.
|
||
|
||
Returns the accumulator.
|
||
|
||
Note that since the first element of the enumerable is used as the
|
||
initial value of the accumulator, `fun` will only be executed `n - 1`
|
||
times where `n` is the length of the enumerable.
|
||
|
||
## Examples
|
||
|
||
iex> Enum.reduce([1, 2, 3, 4], fn(x, acc) -> x * acc end)
|
||
24
|
||
|
||
"""
|
||
@spec reduce(t, (element, any -> any)) :: any
|
||
def reduce(enumerable, fun)
|
||
|
||
def reduce([h | t], fun) do
|
||
reduce(t, h, fun)
|
||
end
|
||
|
||
def reduce([], _fun) do
|
||
raise Enum.EmptyError
|
||
end
|
||
|
||
def reduce(enumerable, fun) do
|
||
result =
|
||
Enumerable.reduce(enumerable, {: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 """
|
||
Invokes `fun` for each element in the `enumerable`, 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(enumerable, acc, fun) when is_list(enumerable) do
|
||
:lists.foldl(fun, acc, enumerable)
|
||
end
|
||
|
||
def reduce(%{__struct__: _} = enumerable, acc, fun) do
|
||
Enumerable.reduce(enumerable, {:cont, acc},
|
||
fn x, acc -> {:cont, fun.(x, acc)} end) |> elem(1)
|
||
end
|
||
|
||
def reduce(%{} = enumerable, acc, fun) do
|
||
:maps.fold(fn k, v, acc -> fun.({k, v}, acc) end, acc, enumerable)
|
||
end
|
||
|
||
def reduce(enumerable, acc, fun) do
|
||
Enumerable.reduce(enumerable, {:cont, acc},
|
||
fn x, acc -> {:cont, fun.(x, acc)} end) |> elem(1)
|
||
end
|
||
|
||
@doc """
|
||
Reduces the enumerable until `halt` is emitted.
|
||
|
||
The return value for `fun` is expected to be `{:cont, acc}`, return
|
||
`{:halt, acc}` to end the reduction early.
|
||
|
||
Returns the accumulator.
|
||
|
||
## Examples
|
||
|
||
iex> Enum.reduce_while(1..100, 0, fn i, acc ->
|
||
...> if i < 3, do: {:cont, acc + i}, else: {:halt, acc}
|
||
...> end)
|
||
3
|
||
|
||
"""
|
||
def reduce_while(enumerable, acc, fun) do
|
||
Enumerable.reduce(enumerable, {:cont, acc}, fun) |> elem(1)
|
||
end
|
||
|
||
@doc """
|
||
Returns elements of `enumerable` for which the function `fun` returns
|
||
`false` or `nil`.
|
||
|
||
See also `filter/2`.
|
||
|
||
## 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(enumerable, fun) when is_list(enumerable) do
|
||
for item <- enumerable, !fun.(item), do: item
|
||
end
|
||
|
||
def reject(enumerable, fun) do
|
||
reduce(enumerable, [], R.reject(fun)) |> :lists.reverse
|
||
end
|
||
|
||
@doc """
|
||
Returns a list of elements in `enumerable` in reverse order.
|
||
|
||
## Examples
|
||
|
||
iex> Enum.reverse([1, 2, 3])
|
||
[3, 2, 1]
|
||
|
||
"""
|
||
@spec reverse(t) :: list
|
||
def reverse(enumerable) when is_list(enumerable) do
|
||
:lists.reverse(enumerable)
|
||
end
|
||
|
||
def reverse(enumerable) do
|
||
reverse(enumerable, [])
|
||
end
|
||
|
||
@doc """
|
||
Reverses the elements in `enumerable`, appends the tail, and returns
|
||
it as a list.
|
||
|
||
This is an optimization for
|
||
`Enum.concat(Enum.reverse(enumerable), tail)`.
|
||
|
||
## Examples
|
||
|
||
iex> Enum.reverse([1, 2, 3], [4, 5, 6])
|
||
[3, 2, 1, 4, 5, 6]
|
||
|
||
"""
|
||
@spec reverse(t, t) :: list
|
||
def reverse(enumerable, tail) when is_list(enumerable)
|
||
and is_list(tail) do
|
||
:lists.reverse(enumerable, tail)
|
||
end
|
||
|
||
def reverse(enumerable, tail) do
|
||
reduce(enumerable, to_list(tail), fn(entry, acc) ->
|
||
[entry | acc]
|
||
end)
|
||
end
|
||
|
||
@doc """
|
||
Reverses the enumerable in the range from initial position `start`
|
||
through `count` elements.
|
||
|
||
If `count` is greater than the size of the rest of the enumerable,
|
||
then this function will reverse the rest of the enumerable.
|
||
|
||
## Examples
|
||
|
||
iex> Enum.reverse_slice([1, 2, 3, 4, 5, 6], 2, 4)
|
||
[1, 2, 6, 5, 4, 3]
|
||
|
||
"""
|
||
@spec reverse_slice(t, non_neg_integer, non_neg_integer) :: list
|
||
def reverse_slice(enumerable, start, count) when start >= 0
|
||
and count >= 0 do
|
||
list = reverse(enumerable)
|
||
length = length(list)
|
||
count = Kernel.min(count, length - start)
|
||
|
||
if count > 0 do
|
||
reverse_slice(list, length, start + count, count, [])
|
||
else
|
||
:lists.reverse(list)
|
||
end
|
||
end
|
||
|
||
@doc """
|
||
Applies the given function to each element in the enumerable,
|
||
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(enumerable, fun) do
|
||
{res, _} = reduce(enumerable, {[], :first}, R.scan_2(fun))
|
||
:lists.reverse(res)
|
||
end
|
||
|
||
@doc """
|
||
Applies the given function to each element in the enumerable,
|
||
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(enumerable, acc, fun) do
|
||
{res, _} = reduce(enumerable, {[], acc}, R.scan_3(fun))
|
||
:lists.reverse(res)
|
||
end
|
||
|
||
@doc """
|
||
Returns a list with the elements of `enumerable` shuffled.
|
||
|
||
This function uses Erlang's `:rand` module to calculate
|
||
the random value. Check its documentation for setting a
|
||
different random algorithm or a different seed.
|
||
|
||
## Examples
|
||
|
||
# Although not necessary, let's seed the random algorithm
|
||
iex> :rand.seed(:exsplus, {1, 2, 3})
|
||
iex> Enum.shuffle([1, 2, 3])
|
||
[2, 1, 3]
|
||
iex> Enum.shuffle([1, 2, 3])
|
||
[2, 3, 1]
|
||
|
||
"""
|
||
@spec shuffle(t) :: list
|
||
def shuffle(enumerable) do
|
||
randomized = reduce(enumerable, [], fn x, acc ->
|
||
[{:rand.uniform, x} | acc]
|
||
end)
|
||
unwrap(:lists.keysort(1, randomized), [])
|
||
end
|
||
|
||
@doc """
|
||
Returns a subset list of the given enumerable. 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
|
||
enumerable to negative positions (e.g. position -3 in an
|
||
enumerable with count 5 becomes position 2).
|
||
|
||
The first position (after adding count to negative positions) must be
|
||
smaller or equal to the last position.
|
||
|
||
If the start of the range is not a valid offset for the given
|
||
enumerable or if the range is in reverse order, returns `[]`.
|
||
|
||
## Examples
|
||
|
||
iex> Enum.slice(1..100, 5..10)
|
||
[6, 7, 8, 9, 10, 11]
|
||
|
||
iex> Enum.slice(1..10, 5..20)
|
||
[6, 7, 8, 9, 10]
|
||
|
||
iex> Enum.slice(1..10, 11..20)
|
||
[]
|
||
|
||
iex> Enum.slice(1..10, 6..5)
|
||
[]
|
||
|
||
"""
|
||
@spec slice(t, Range.t) :: list
|
||
def slice(enumerable, range)
|
||
|
||
def slice(enumerable, first..last) when first >= 0 and last >= 0 do
|
||
# Simple case, which works on infinite enumerables
|
||
if last - first >= 0 do
|
||
slice(enumerable, first, last - first + 1)
|
||
else
|
||
[]
|
||
end
|
||
end
|
||
|
||
def slice(enumerable, first..last) do
|
||
{list, count} = enumerate_and_count(enumerable, 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)
|
||
else
|
||
[]
|
||
end
|
||
end
|
||
|
||
@doc """
|
||
Returns a subset list of the given enumerable. Drops elements
|
||
until element position `start`, then takes `count` elements.
|
||
|
||
If the count is greater than `enumerable` length, it returns as
|
||
many as possible. If zero, then it returns `[]`.
|
||
|
||
## Examples
|
||
|
||
iex> Enum.slice(1..100, 5, 10)
|
||
[6, 7, 8, 9, 10, 11, 12, 13, 14, 15]
|
||
|
||
iex> Enum.slice(1..10, 5, 100)
|
||
[6, 7, 8, 9, 10]
|
||
|
||
iex> Enum.slice(1..10, 5, 0)
|
||
[]
|
||
|
||
"""
|
||
@spec slice(t, integer, non_neg_integer) :: list
|
||
|
||
def slice(_enumerable, start, 0) when is_integer(start), do: []
|
||
|
||
def slice(enumerable, start, count) when is_integer(start)
|
||
and start < 0 and is_integer(count) and count >= 0 do
|
||
{list, new_start} = enumerate_and_count(enumerable, start)
|
||
if new_start >= 0 do
|
||
slice(list, new_start, count)
|
||
else
|
||
[]
|
||
end
|
||
end
|
||
|
||
def slice(enumerable, start, count) when is_list(enumerable)
|
||
and is_integer(start) and start >= 0 and is_integer(count)
|
||
and count > 0 do
|
||
do_slice(enumerable, start, count)
|
||
end
|
||
|
||
def slice(enumerable, start, count) when is_integer(start)
|
||
and start >= 0 and is_integer(count) and count > 0 do
|
||
{_, _, list} = Enumerable.reduce(enumerable,
|
||
{: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)
|
||
|
||
:lists.reverse(list)
|
||
end
|
||
|
||
@doc """
|
||
Sorts the enumerable according to Erlang's term ordering.
|
||
|
||
Uses the merge sort algorithm.
|
||
|
||
## Examples
|
||
|
||
iex> Enum.sort([3, 2, 1])
|
||
[1, 2, 3]
|
||
|
||
"""
|
||
@spec sort(t) :: list
|
||
def sort(enumerable) when is_list(enumerable) do
|
||
:lists.sort(enumerable)
|
||
end
|
||
|
||
def sort(enumerable) do
|
||
sort(enumerable, &(&1 <= &2))
|
||
end
|
||
|
||
@doc """
|
||
Sorts the enumerable by the given function.
|
||
|
||
This function uses the merge sort algorithm. The given function should compare
|
||
two arguments, and return `false` if the first argument follows the second 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` for equal values, the sorting
|
||
is not stable and the order of equal terms may be shuffled.
|
||
For example:
|
||
|
||
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(enumerable, fun) when is_list(enumerable) do
|
||
:lists.sort(fun, enumerable)
|
||
end
|
||
|
||
def sort(enumerable, fun) do
|
||
reduce(enumerable, [], &sort_reducer(&1, &2, fun))
|
||
|> sort_terminator(fun)
|
||
end
|
||
|
||
@doc """
|
||
Sorts the mapped results of the enumerable according to the `sorter`
|
||
function.
|
||
|
||
This function maps each element of the enumerable using the `mapper`
|
||
function. The enumerable is then sorted by the mapped elements
|
||
using the `sorter` function, which defaults to `Kernel.<=/2`
|
||
|
||
`sort_by/3` differs from `sort/2` in that it only calculates the
|
||
comparison value for each element in the enumerable once instead of
|
||
once for each element in each comparison.
|
||
If the same function is being called on both element, it's also more
|
||
compact to use `sort_by/3`.
|
||
|
||
This technique is also known as a
|
||
_[Schwartzian Transform](https://en.wikipedia.org/wiki/Schwartzian_transform)_,
|
||
or the _Lisp decorate-sort-undecorate idiom_ as the `mapper`
|
||
is decorating the original `enumerable`; then `sorter` is sorting the
|
||
decorations; and finally the enumerable is being undecorated so only
|
||
the original elements remain, but now in sorted order.
|
||
|
||
## Examples
|
||
|
||
Using the default `sorter` of `<=/2`:
|
||
|
||
iex> Enum.sort_by ["some", "kind", "of", "monster"], &byte_size/1
|
||
["of", "some", "kind", "monster"]
|
||
|
||
Using a custom `sorter` to override the order:
|
||
|
||
iex> Enum.sort_by ["some", "kind", "of", "monster"], &byte_size/1, &>=/2
|
||
["monster", "some", "kind", "of"]
|
||
|
||
"""
|
||
@spec sort_by(t, (element -> mapped_element),
|
||
(mapped_element, mapped_element -> boolean))
|
||
:: list when mapped_element: element
|
||
|
||
def sort_by(enumerable, mapper, sorter \\ &<=/2) do
|
||
enumerable
|
||
|> map(&{&1, mapper.(&1)})
|
||
|> sort(&sorter.(elem(&1, 1), elem(&2, 1)))
|
||
|> map(&elem(&1, 0))
|
||
end
|
||
|
||
@doc """
|
||
Splits the `enumerable` into two enumerables, leaving `count`
|
||
elements in the first one. If `count` is a negative number,
|
||
it starts counting from the back to the beginning of the
|
||
enumerable.
|
||
|
||
Be aware that a negative `count` implies the `enumerable`
|
||
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(enumerable, count) when is_list(enumerable) and count >= 0 do
|
||
do_split(enumerable, count, [])
|
||
end
|
||
|
||
def split(enumerable, count) when count >= 0 do
|
||
{_, list1, list2} =
|
||
reduce(enumerable, {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(enumerable, count) when count < 0 do
|
||
do_split_reverse(reverse(enumerable), abs(count), [])
|
||
end
|
||
|
||
@doc """
|
||
Splits enumerable in two at the position of the element for which
|
||
`fun` returns `false` for the first time.
|
||
|
||
## 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(enumerable, fun) when is_list(enumerable) do
|
||
do_split_while(enumerable, fun, [])
|
||
end
|
||
|
||
def split_while(enumerable, fun) do
|
||
{list1, list2} =
|
||
reduce(enumerable, {[], []}, 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 """
|
||
Returns the sum of all elements.
|
||
|
||
Raises `ArithmeticError` if `enumerable` contains a non-numeric value.
|
||
|
||
## Examples
|
||
|
||
iex> Enum.sum([1, 2, 3])
|
||
6
|
||
|
||
"""
|
||
@spec sum(t) :: number
|
||
def sum(enumerable)
|
||
|
||
def sum(first..first),
|
||
do: first
|
||
|
||
def sum(first..last) when last < first,
|
||
do: sum(last..first)
|
||
|
||
def sum(first..last) when last > first do
|
||
div((last + first) * (last - first + 1), 2)
|
||
end
|
||
|
||
def sum(enumerable) do
|
||
reduce(enumerable, 0, &+/2)
|
||
end
|
||
|
||
@doc """
|
||
Takes the first `count` items from the enumerable.
|
||
|
||
`count` must be an integer. If a negative `count` is given, the last
|
||
`count` values will be taken.
|
||
For such, the enumerable is fully enumerated keeping up
|
||
to `2 * count` elements in memory. Once the end of the enumerable 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(enumerable, count)
|
||
|
||
def take(_enumerable, 0), do: []
|
||
def take([], _count), do: []
|
||
|
||
def take(enumerable, count) when is_list(enumerable)
|
||
and is_integer(count) and count > 0 do
|
||
do_take(enumerable, count, [])
|
||
end
|
||
|
||
def take(enumerable, count) when is_integer(count) and count > 0 do
|
||
{_, {res, _}} =
|
||
Enumerable.reduce(enumerable, {:cont, {[], count}},
|
||
fn(entry, {list, n}) ->
|
||
case n do
|
||
0 -> {:halt, {list, n}}
|
||
1 -> {:halt, {[entry | list], n - 1}}
|
||
_ -> {:cont, {[entry | list], n - 1}}
|
||
end
|
||
end)
|
||
:lists.reverse(res)
|
||
end
|
||
|
||
def take(enumerable, count) when is_integer(count) and count < 0 do
|
||
count = abs(count)
|
||
|
||
{_count, buf1, buf2} =
|
||
reduce(enumerable, {0, [], []}, fn entry, {n, buf1, buf2} ->
|
||
buf1 = [entry | buf1]
|
||
n = n + 1
|
||
if n == count do
|
||
{0, [], buf1}
|
||
else
|
||
{n, buf1, buf2}
|
||
end
|
||
end)
|
||
|
||
do_take_last(buf1, buf2, count, [])
|
||
end
|
||
|
||
defp do_take_last(_buf1, _buf2, 0, acc),
|
||
do: acc
|
||
defp do_take_last([], [], _, acc),
|
||
do: acc
|
||
defp do_take_last([], [h | t], count, acc),
|
||
do: do_take_last([], t, count-1, [h | acc])
|
||
defp do_take_last([h | t], buf2, count, acc),
|
||
do: do_take_last(t, buf2, count-1, [h | acc])
|
||
|
||
@doc """
|
||
Returns a list of every `nth` item in the enumerable,
|
||
starting with the first element.
|
||
|
||
The first item is always included, unless `nth` is 0.
|
||
|
||
The second argument specifying every `nth` item must be a non-negative
|
||
integer, otherwise `FunctionClauseError` will be raised.
|
||
|
||
## Examples
|
||
|
||
iex> Enum.take_every(1..10, 2)
|
||
[1, 3, 5, 7, 9]
|
||
|
||
iex> Enum.take_every(1..10, 0)
|
||
[]
|
||
|
||
iex> Enum.take_every([1, 2, 3], 1)
|
||
[1, 2, 3]
|
||
|
||
"""
|
||
@spec take_every(t, non_neg_integer) :: list | no_return
|
||
def take_every(enumerable, nth)
|
||
|
||
def take_every(enumerable, 1), do: to_list(enumerable)
|
||
def take_every(_enumerable, 0), do: []
|
||
def take_every([], _nth), do: []
|
||
|
||
def take_every(enumerable, nth) when is_integer(nth) and nth > 0 do
|
||
{res, _} = reduce(enumerable, {[], :first}, R.take_every(nth))
|
||
:lists.reverse(res)
|
||
end
|
||
|
||
@doc """
|
||
Takes random items from `enumerable`.
|
||
|
||
Notice this function will traverse the whole `enumerable` to
|
||
get the random sublist.
|
||
|
||
See `random/1` for notes on implementation and random seed.
|
||
|
||
## Examples
|
||
|
||
# Although not necessary, let's seed the random algorithm
|
||
iex> :rand.seed(:exsplus, {1, 2, 3})
|
||
iex> Enum.take_random(1..10, 2)
|
||
[5, 8]
|
||
iex> Enum.take_random(?a..?z, 5)
|
||
'fhjni'
|
||
|
||
"""
|
||
@spec take_random(t, non_neg_integer) :: list
|
||
def take_random(_enumerable, 0), do: []
|
||
def take_random(first..first, 1),
|
||
do: [first]
|
||
def take_random(first..last, 1) when first > last,
|
||
do: take_random(last..first, 1)
|
||
def take_random(first..last, 1),
|
||
do: [random_index(last - first) + first]
|
||
|
||
def take_random(enumerable, count) when is_integer(count) and count > 128 do
|
||
reducer = fn(elem, {idx, sample}) ->
|
||
jdx = random_index(idx)
|
||
cond do
|
||
idx < count ->
|
||
value = Map.get(sample, jdx)
|
||
{idx + 1, Map.put(sample, idx, value) |> Map.put(jdx, elem)}
|
||
jdx < count ->
|
||
{idx + 1, Map.put(sample, jdx, elem)}
|
||
true ->
|
||
{idx + 1, sample}
|
||
end
|
||
end
|
||
|
||
{size, sample} = reduce(enumerable, {0, %{}}, reducer)
|
||
take_random(sample, Kernel.min(count, size), [])
|
||
end
|
||
|
||
def take_random(enumerable, count) when is_integer(count) and count > 0 do
|
||
sample = Tuple.duplicate(nil, count)
|
||
|
||
reducer = fn(elem, {idx, sample}) ->
|
||
jdx = random_index(idx)
|
||
cond do
|
||
idx < count ->
|
||
value = elem(sample, jdx)
|
||
{idx + 1, put_elem(sample, idx, value) |> put_elem(jdx, elem)}
|
||
jdx < count ->
|
||
{idx + 1, put_elem(sample, jdx, elem)}
|
||
true ->
|
||
{idx + 1, sample}
|
||
end
|
||
end
|
||
|
||
{size, sample} = reduce(enumerable, {0, sample}, reducer)
|
||
sample |> Tuple.to_list |> take(Kernel.min(count, size))
|
||
end
|
||
|
||
defp take_random(_sample, 0, acc), do: acc
|
||
|
||
defp take_random(sample, position, acc) do
|
||
position = position - 1
|
||
take_random(sample, position, [Map.get(sample, position) | acc])
|
||
end
|
||
|
||
@doc """
|
||
Takes the items from the beginning of the enumerable while `fun` returns
|
||
a truthy value.
|
||
|
||
## 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(enumerable, fun) when is_list(enumerable) do
|
||
do_take_while(enumerable, fun, [])
|
||
end
|
||
|
||
def take_while(enumerable, fun) do
|
||
{_, res} =
|
||
Enumerable.reduce(enumerable, {:cont, []}, fn(entry, acc) ->
|
||
if fun.(entry) do
|
||
{:cont, [entry | acc]}
|
||
else
|
||
{:halt, acc}
|
||
end
|
||
end)
|
||
|
||
:lists.reverse(res)
|
||
end
|
||
|
||
@doc """
|
||
Converts `enumerable` to a list.
|
||
|
||
## Examples
|
||
|
||
iex> Enum.to_list(1..3)
|
||
[1, 2, 3]
|
||
|
||
"""
|
||
@spec to_list(t) :: [element]
|
||
def to_list(enumerable) when is_list(enumerable) do
|
||
enumerable
|
||
end
|
||
|
||
def to_list(enumerable) do
|
||
reverse(enumerable) |> :lists.reverse
|
||
end
|
||
|
||
@doc """
|
||
Enumerates the `enumerable`, removing all duplicated elements.
|
||
|
||
## Examples
|
||
|
||
iex> Enum.uniq([1, 2, 3, 3, 2, 1])
|
||
[1, 2, 3]
|
||
|
||
"""
|
||
@spec uniq(t) :: list
|
||
def uniq(enumerable) do
|
||
uniq_by(enumerable, fn x -> x end)
|
||
end
|
||
|
||
@doc false
|
||
# TODO: Deprecate by 1.4
|
||
def uniq(enumerable, fun) do
|
||
uniq_by(enumerable, fun)
|
||
end
|
||
|
||
@doc """
|
||
Enumerates the `enumerable`, by removing the elements for which
|
||
function `fun` returned duplicate items.
|
||
|
||
The function `fun` maps every element to a term which is used to
|
||
determine if two elements are duplicates.
|
||
## Example
|
||
|
||
iex> Enum.uniq_by([{1, :x}, {2, :y}, {1, :z}], fn {x, _} -> x end)
|
||
[{1, :x}, {2, :y}]
|
||
|
||
iex> Enum.uniq_by([a: {:tea, 2}, b: {:tea, 2}, c: {:coffee, 1}], fn {_, y} -> y end)
|
||
[a: {:tea, 2}, c: {:coffee, 1}]
|
||
|
||
"""
|
||
@spec uniq_by(t, (element -> term)) :: list
|
||
|
||
def uniq_by(enumerable, fun) when is_list(enumerable) do
|
||
do_uniq(enumerable, %{}, fun, [])
|
||
end
|
||
|
||
def uniq_by(enumerable, fun) do
|
||
{list, _} = reduce(enumerable, {[], %{}}, R.uniq(fun))
|
||
:lists.reverse(list)
|
||
end
|
||
|
||
@doc """
|
||
Opposite of `Enum.zip/2`; extracts a two-element tuples from the
|
||
enumerable and groups them together.
|
||
|
||
It takes an enumerable with items being two-element tuples and returns
|
||
a tuple with two lists, each of which is formed by the first and
|
||
second element of each tuple, respectively.
|
||
|
||
This function fails unless `enumerable` is or can be converted into a
|
||
list of tuples with *exactly* two elements in each tuple.
|
||
|
||
## Examples
|
||
|
||
iex> Enum.unzip([{:a, 1}, {:b, 2}, {:c, 3}])
|
||
{[:a, :b, :c], [1, 2, 3]}
|
||
|
||
iex> Enum.unzip(%{a: 1, b: 2})
|
||
{[:a, :b], [1, 2]}
|
||
|
||
"""
|
||
@spec unzip(t) :: {[element], [element]}
|
||
def unzip(enumerable) do
|
||
{list1, list2} = reduce(enumerable, {[], []},
|
||
fn({el1, el2}, {list1, list2}) ->
|
||
{[el1 | list1], [el2 | list2]}
|
||
end)
|
||
|
||
{:lists.reverse(list1), :lists.reverse(list2)}
|
||
end
|
||
|
||
@doc """
|
||
Returns the enumerable with each element wrapped in a tuple
|
||
alongside its index.
|
||
|
||
## Examples
|
||
|
||
iex> Enum.with_index([:a, :b, :c])
|
||
[a: 0, b: 1, c: 2]
|
||
|
||
iex> Enum.with_index([:a, :b, :c], 3)
|
||
[a: 3, b: 4, c: 5]
|
||
|
||
"""
|
||
@spec with_index(t) :: [{element, integer}]
|
||
@spec with_index(t, integer) :: [{element, integer}]
|
||
def with_index(enumerable, offset \\ 0) do
|
||
map_reduce(enumerable, offset, fn x, acc ->
|
||
{{x, acc}, acc + 1}
|
||
end) |> elem(0)
|
||
end
|
||
|
||
@doc """
|
||
Zips corresponding elements from two enumerables 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(enumerable1, enumerable2) when is_list(enumerable1)
|
||
and is_list(enumerable2) do
|
||
do_zip(enumerable1, enumerable2, [])
|
||
end
|
||
|
||
def zip(enumerable1, enumerable2) do
|
||
Stream.zip(enumerable1, enumerable2).({:cont, []}, &{:cont, [&1 | &2]})
|
||
|> elem(1)
|
||
|> :lists.reverse
|
||
end
|
||
|
||
## Helpers
|
||
|
||
@compile {:inline, enum_to_string: 1}
|
||
|
||
defp enumerate_and_count(enumerable, count) when is_list(enumerable) do
|
||
{enumerable, length(enumerable) - abs(count)}
|
||
end
|
||
|
||
defp enumerate_and_count(enumerable, count) do
|
||
map_reduce(enumerable, -abs(count), fn(x, acc) -> {x, acc + 1} end)
|
||
end
|
||
|
||
defp enum_to_string(entry) when is_binary(entry), do: entry
|
||
defp enum_to_string(entry), do: String.Chars.to_string(entry)
|
||
|
||
defp random_index(n) do
|
||
:rand.uniform(n + 1) - 1
|
||
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
|
||
|
||
## 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
|
||
|
||
## 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
|
||
|
||
## find
|
||
|
||
defp do_find([h | t], default, fun) do
|
||
if fun.(h) do
|
||
h
|
||
else
|
||
do_find(t, default, fun)
|
||
end
|
||
end
|
||
|
||
defp do_find([], default, _) do
|
||
default
|
||
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], default, fun) do
|
||
fun.(h) || do_find_value(t, default, fun)
|
||
end
|
||
|
||
defp do_find_value([], default, _) do
|
||
default
|
||
end
|
||
|
||
## shuffle
|
||
|
||
defp unwrap([{_, h} | enumerable], t) do
|
||
unwrap(enumerable, [h | t])
|
||
end
|
||
|
||
defp unwrap([], t), do: t
|
||
|
||
## reverse_slice
|
||
|
||
defp reverse_slice(rest, idx, idx, count, acc) do
|
||
{slice, rest} = head_slice(rest, count, [])
|
||
|
||
:lists.reverse(rest, :lists.reverse(slice, acc))
|
||
end
|
||
|
||
defp reverse_slice([elem | rest], idx, start, count, acc) do
|
||
reverse_slice(rest, idx - 1, start, count, [elem | acc])
|
||
end
|
||
|
||
defp head_slice(rest, 0, acc), do: {acc, rest}
|
||
|
||
defp head_slice([elem | rest], count, acc) do
|
||
head_slice(rest, count - 1, [elem | acc])
|
||
end
|
||
|
||
## slice
|
||
|
||
defp do_slice([], _start, _count) do
|
||
[]
|
||
end
|
||
|
||
defp do_slice(_list, _start, 0) 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
|
||
|
||
## 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, acc) when counter > 0 do
|
||
do_take(t, counter - 1, [h | acc])
|
||
end
|
||
|
||
defp do_take(_list, 0, acc) do
|
||
:lists.reverse(acc)
|
||
end
|
||
|
||
defp do_take([], _, acc) do
|
||
:lists.reverse(acc)
|
||
end
|
||
|
||
## take_while
|
||
|
||
defp do_take_while([h | t], fun, acc) do
|
||
if fun.(h) do
|
||
do_take_while(t, fun, [h | acc])
|
||
else
|
||
:lists.reverse(acc)
|
||
end
|
||
end
|
||
|
||
defp do_take_while([], _, acc) do
|
||
:lists.reverse(acc)
|
||
end
|
||
|
||
## uniq
|
||
|
||
defp do_uniq([h | t], set, fun, acc) do
|
||
value = fun.(h)
|
||
case set do
|
||
%{^value => true} -> do_uniq(t, set, fun, acc)
|
||
%{} -> do_uniq(t, Map.put(set, value, true), fun, [h | acc])
|
||
end
|
||
end
|
||
|
||
defp do_uniq([], _set, _fun, acc) do
|
||
:lists.reverse(acc)
|
||
end
|
||
|
||
## zip
|
||
|
||
defp do_zip([h1 | next1], [h2 | next2], acc) do
|
||
do_zip(next1, next2, [{h1, h2} | acc])
|
||
end
|
||
|
||
defp do_zip(_, [], acc), do: :lists.reverse(acc)
|
||
defp do_zip([], _, acc), do: :lists.reverse(acc)
|
||
end
|
||
|
||
defimpl Enumerable, for: List do
|
||
def count(_list),
|
||
do: {:error, __MODULE__}
|
||
|
||
def member?(_list, _value),
|
||
do: {:error, __MODULE__}
|
||
|
||
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)
|
||
end
|
||
|
||
defimpl Enumerable, for: Map do
|
||
def count(map) do
|
||
{:ok, map_size(map)}
|
||
end
|
||
|
||
def member?(map, {key, value}) do
|
||
{:ok, match?({:ok, ^value}, :maps.find(key, map))}
|
||
end
|
||
|
||
def member?(_map, _other) do
|
||
{:ok, false}
|
||
end
|
||
|
||
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)
|
||
end
|
||
|
||
defimpl Enumerable, for: Function do
|
||
def count(_function),
|
||
do: {:error, __MODULE__}
|
||
|
||
def member?(_function, _value),
|
||
do: {:error, __MODULE__}
|
||
|
||
def reduce(function, acc, fun) when is_function(function, 2),
|
||
do: function.(acc, fun)
|
||
end
|