1652 lines
40 KiB
Elixir
1652 lines
40 KiB
Elixir
defprotocol Enumerable do
|
|
@moduledoc """
|
|
This is the protocol used by the `Enum` module.
|
|
|
|
Usually, when you invoke a function in the module `Enum`, the first argument
|
|
passed to it is a collection which is forwarded to this protocol in order to
|
|
perform operations on the collection.
|
|
|
|
For example, in the expression
|
|
|
|
Enum.map([1, 2, 3], &(&1 * 2))
|
|
|
|
`Enum.map/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 cons'ing the element with the accumulated list.
|
|
"""
|
|
|
|
@only [List, Record, Function]
|
|
|
|
@doc """
|
|
This function performs the reducing operation on a given collection. It
|
|
returns the accumulated value of applying the given function `fun` on every
|
|
element with the accumulated value.
|
|
|
|
As an example, here is the implementation of `reduce` for lists:
|
|
|
|
def reduce([h|t], acc, fun), do: reduce(t, fun.(h, acc), fun)
|
|
def reduce([], acc, _fun), do: acc
|
|
|
|
As an additional example, here is the implementation of `Enum.map/2` with
|
|
`Enumerable`:
|
|
|
|
def map(collection, fun) do
|
|
Enumerable.reduce(collection, [], fn(entry, acc) ->
|
|
[fun.(entry)|acc]
|
|
end) |> :lists.reverse
|
|
end
|
|
"""
|
|
def reduce(collection, acc, fun)
|
|
|
|
@doc """
|
|
The function is used to check if a value exists within the collection.
|
|
"""
|
|
def member?(collection, value)
|
|
|
|
@doc """
|
|
The function is used to retrieve the collection's size.
|
|
"""
|
|
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. Most of the functions in this module have two
|
|
flavours. If a given collection implements the mentioned protocol (like
|
|
`List`, for instance), you can do:
|
|
|
|
Enum.map([1, 2, 3], fn(x) -> x * 2 end)
|
|
|
|
Depending on the type of the collection, the user-provided function will
|
|
accept a certain type of argument. For dicts, the argument is always a
|
|
`{ key, value }` tuple.
|
|
"""
|
|
|
|
@compile :inline_list_funcs
|
|
|
|
@type t :: Enumerable.t
|
|
@type element :: any
|
|
@type index :: non_neg_integer
|
|
@type default :: any
|
|
|
|
@doc """
|
|
Checks if `value` exists within the `collection`.
|
|
|
|
## 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) do
|
|
Enumerable.member?(collection, value)
|
|
end
|
|
|
|
@doc """
|
|
Returns the collection's size.
|
|
|
|
## Examples
|
|
|
|
iex> Enum.count([1, 2, 3])
|
|
3
|
|
|
|
"""
|
|
@spec count(t) :: non_neg_integer
|
|
def count(collection) do
|
|
Enumerable.count(collection)
|
|
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, 0, fn(entry, acc) ->
|
|
if fun.(entry), do: acc + 1, else: acc
|
|
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, true, fn(entry, _) ->
|
|
if fun.(entry), do: true, else: throw(:enum_all?)
|
|
end)
|
|
catch
|
|
:enum_all? -> false
|
|
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, false, fn(entry, _) ->
|
|
if fun.(entry), do: throw(:enum_any?), else: false
|
|
end)
|
|
catch
|
|
:enum_any? -> true
|
|
end
|
|
|
|
@doc """
|
|
Finds the element at the given index (zero-based).
|
|
Returns `default` if index is out of bounds.
|
|
|
|
Expects an ordered collection.
|
|
|
|
## 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, index) :: element | nil
|
|
@spec at(t, index, default) :: element | default
|
|
def at(collection, n, default // nil) when n >= 0 do
|
|
case fetch(collection, n) do
|
|
{ :ok, h } -> h
|
|
:error -> default
|
|
end
|
|
end
|
|
|
|
@doc """
|
|
Drops the first `count` items from `collection`.
|
|
Expects an ordered collection.
|
|
|
|
## 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]
|
|
|
|
"""
|
|
@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
|
|
{ list, _ } = Enumerable.reduce(collection, { [], count }, fn
|
|
_, { _, count } when count > 0 -> { [], count - 1 }
|
|
entry, { list, count } -> { [entry|list], count }
|
|
end)
|
|
:lists.reverse(list)
|
|
end
|
|
|
|
def drop(collection, count) when count < 0 do
|
|
{ list, count } = iterate_and_count(collection, count)
|
|
drop(list, count)
|
|
end
|
|
|
|
@doc """
|
|
Drops items at the beginning of `collection` while `fun` returns `true`.
|
|
Expects an ordered collection.
|
|
|
|
## 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
|
|
Enumerable.reduce(collection, [], fn
|
|
entry, [] -> if fun.(entry), do: [], else: [entry]
|
|
entry, acc -> [entry|acc]
|
|
end) |> :lists.reverse
|
|
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
|
|
Enumerable.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, true, fn(_, _) -> throw(:enum_empty?) end)
|
|
catch
|
|
:enum_empty? -> false
|
|
end
|
|
|
|
@doc """
|
|
Finds the element at the given index (zero-based).
|
|
Returns `{ :ok, element }` if found, otherwise `:error`.
|
|
|
|
Expects an ordered collection.
|
|
|
|
## 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, index) :: { :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
|
|
Enumerable.reduce(collection, 0, fn(entry, acc) ->
|
|
if acc == n do
|
|
throw({ :enum_fetch, entry })
|
|
else
|
|
acc + 1
|
|
end
|
|
end)
|
|
|
|
:error
|
|
catch
|
|
{ :enum_fetch, entry } -> { :ok, entry }
|
|
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, index) :: element | no_return
|
|
def fetch!(collection, n) when n >= 0 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
|
|
lc item inlist collection, fun.(item), do: item
|
|
end
|
|
|
|
def filter(collection, fun) do
|
|
Enumerable.reduce(collection, [], fn(entry, acc) ->
|
|
if fun.(entry), do: [entry|acc], else: acc
|
|
end) |> :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
|
|
lc item inlist collection, filter.(item), do: mapper.(item)
|
|
end
|
|
|
|
def filter_map(collection, filter, mapper) do
|
|
Enumerable.reduce(collection, [], fn(entry, acc) ->
|
|
if filter.(entry), do: [mapper.(entry)|acc], else: acc
|
|
end) |> :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, ifnone, fn(entry, _) ->
|
|
if fun.(entry), do: throw({ :enum_find, entry })
|
|
end)
|
|
catch
|
|
{ :enum_find, entry } -> entry
|
|
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, ifnone, fn(entry, _) ->
|
|
fun_entry = fun.(entry)
|
|
if fun_entry, do: throw({ :enum_find, fun_entry })
|
|
end)
|
|
catch
|
|
{ :enum_find, entry } -> entry
|
|
end
|
|
|
|
@doc """
|
|
Similar to `find/3`, but returns the index (zero-based)
|
|
of the element instead of the element itself.
|
|
|
|
Expects an ordered collection.
|
|
|
|
## 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
|
|
Enumerable.reduce(collection, 0, fn(entry, acc) ->
|
|
if fun.(entry), do: throw({ :enum_find_index, acc }), else: acc + 1
|
|
end)
|
|
nil
|
|
catch
|
|
{ :enum_find_index, idx } -> idx
|
|
end
|
|
|
|
@doc """
|
|
Returns the first item in the collection or `nil` otherwise.
|
|
|
|
## Examples
|
|
|
|
iex> Enum.first([])
|
|
nil
|
|
iex> Enum.first([1, 2, 3])
|
|
1
|
|
|
|
"""
|
|
@spec first(t) :: :nil | element
|
|
def first([]), do: nil
|
|
def first([h|_]), do: h
|
|
|
|
def first(collection) do
|
|
Enumerable.reduce(collection, nil, fn(entry, _) ->
|
|
throw({ :enum_first, entry})
|
|
end)
|
|
catch
|
|
{ :enum_first, entry } -> entry
|
|
end
|
|
|
|
@doc """
|
|
Returns a new collection appending the result of invoking `fun`
|
|
on each corresponding item of `collection`.
|
|
|
|
Given function should return a list.
|
|
|
|
## Examples
|
|
|
|
iex> Enum.flat_map([:a, :b, :c], fn(x) -> [x, x] end)
|
|
[:a, :a, :b, :b, :c, :c]
|
|
|
|
"""
|
|
@spec flat_map(t, (element -> any)) :: list
|
|
def flat_map([], _fun), do: []
|
|
|
|
def flat_map(collection, fun) when is_list(collection) do
|
|
:lists.flatmap(fun, collection)
|
|
end
|
|
|
|
def flat_map(collection, fun) do
|
|
Enumerable.reduce(collection, [], fn(entry, acc) ->
|
|
acc ++ fun.(entry)
|
|
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 | char_list) :: String.t | char_list
|
|
def join(collection, joiner // "")
|
|
|
|
def join(collection, joiner) when is_list(joiner) do
|
|
IO.write "Enum.join/2 with a char list is deprecated, please use do an explicit conversion instead\n#{Exception.format_stacktrace}"
|
|
:unicode.characters_to_list join(collection, :unicode.characters_to_binary(joiner))
|
|
end
|
|
|
|
def join(collection, joiner) when is_binary(joiner) do
|
|
Enumerable.reduce(collection, "", fn
|
|
entry, "" -> to_binary(entry)
|
|
entry, acc -> acc <> joiner <> to_binary(entry)
|
|
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 accepts 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
|
|
lc item inlist collection, do: fun.(item)
|
|
end
|
|
|
|
def map(collection, fun) do
|
|
Enumerable.reduce(collection, [], fn(entry, acc) ->
|
|
[fun.(entry)|acc]
|
|
end) |> :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 | char_list, (element -> any)) :: String.t | char_list
|
|
def map_join(collection, joiner // "", mapper)
|
|
|
|
def map_join(collection, joiner, mapper) when is_list(joiner) do
|
|
IO.write "Enum.map_join/3 with a char list is deprecated, please use do an explicit conversion instead\n#{Exception.format_stacktrace}"
|
|
:unicode.characters_to_list map_join(collection, :unicode.characters_to_binary(joiner), mapper)
|
|
end
|
|
|
|
def map_join(collection, joiner, mapper) when is_binary(joiner) do
|
|
Enumerable.reduce(collection, "", fn
|
|
entry, "" -> to_binary(mapper, entry)
|
|
entry, acc -> acc <> joiner <> to_binary(mapper, entry)
|
|
end)
|
|
end
|
|
|
|
defp to_binary(mapper, entry) do
|
|
case mapper.(entry) do
|
|
x when is_binary(x) -> x
|
|
o -> Binary.Chars.to_binary(o)
|
|
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) do
|
|
{ list, acc } = Enumerable.reduce(collection, { [], acc }, fn(entry, { list, acc }) ->
|
|
{ new_entry, acc } = fun.(entry, acc)
|
|
{ [new_entry|list], acc }
|
|
end)
|
|
{ :lists.reverse(list), acc }
|
|
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 } = Enumerable.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 """
|
|
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) do
|
|
Enumerable.reduce(collection, acc, fun)
|
|
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
|
|
Enum.reduce(t, h, fun)
|
|
end
|
|
|
|
def reduce([], _fun) do
|
|
raise Enum.EmptyError
|
|
end
|
|
|
|
def reduce(collection, fun) do
|
|
result = Enumerable.reduce(collection, :first, fn
|
|
entry, :first -> { :reduce, entry }
|
|
entry, { :reduce, acc } -> { :reduce, fun.(entry, acc) }
|
|
end)
|
|
|
|
case result do
|
|
:first -> raise Enum.EmptyError
|
|
{ :reduce, 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
|
|
lc item inlist collection, !fun.(item), do: item
|
|
end
|
|
|
|
def reject(collection, fun) do
|
|
Enumerable.reduce(collection, [], fn(entry, acc) ->
|
|
unless fun.(entry), do: [entry|acc], else: acc
|
|
end) |> :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
|
|
Enumerable.reduce(collection, [], fn(entry, acc) ->
|
|
[entry|acc]
|
|
end)
|
|
end
|
|
|
|
@doc """
|
|
Returns a list of collection elements shuffled.
|
|
|
|
Notice 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 the following to set a seed
|
|
dynamically:
|
|
|
|
:random.seed(:erlang.now)
|
|
|
|
## Examples
|
|
|
|
iex(1)> Enum.shuffle([1, 2, 3])
|
|
[3, 2, 1]
|
|
iex(2)> Enum.shuffle([1, 2, 3])
|
|
[3, 1, 2]
|
|
|
|
"""
|
|
@spec shuffle(t) :: list
|
|
def shuffle(collection) do
|
|
randomized = Enumerable.reduce(collection, [], fn x, acc ->
|
|
[{ :random.uniform, x }|acc]
|
|
end)
|
|
unwrap(:lists.keysort(1, randomized), [])
|
|
end
|
|
|
|
@doc """
|
|
Returns a sorted list of collection elements. 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
|
|
fun = &1 <= &2
|
|
Enumerable.reduce(collection, [], sort_reducer(&1, &2, fun)) |>
|
|
sort_terminator(fun)
|
|
end
|
|
|
|
@doc """
|
|
Returns a sorted list of collection elements. Uses the merge sort algorithm.
|
|
|
|
## Examples
|
|
|
|
iex> Enum.sort([1, 2, 3], &(&1 > &2))
|
|
[3,2,1]
|
|
|
|
"""
|
|
@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
|
|
Enumerable.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 couting from the back to the beginning of the
|
|
collection.
|
|
|
|
Be aware that a negative `count` implies the collection
|
|
will be iterated 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 } =
|
|
Enumerable.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
|
|
{ list, count } = iterate_and_count(collection, count)
|
|
split(list, 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 } = Enumerable.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. Expects an ordered
|
|
collection.
|
|
|
|
## 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)
|
|
[1,2]
|
|
|
|
"""
|
|
@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
|
|
{ list, _ } = Enumerable.reduce(collection, { [], count }, fn(entry, { list, count }) ->
|
|
if count > 1 do
|
|
{ [entry|list], count - 1 }
|
|
else
|
|
throw({ :enum_take, [entry|list] })
|
|
end
|
|
end)
|
|
:lists.reverse(list)
|
|
catch
|
|
{ :enum_take, list } -> :lists.reverse(list)
|
|
end
|
|
|
|
def take(collection, count) when count < 0 do
|
|
{ list, count } = iterate_and_count(collection, count)
|
|
take(list, count)
|
|
end
|
|
|
|
@doc """
|
|
Takes the items at the beginning of `collection` while `fun` returns `true`.
|
|
Expects an ordered collection.
|
|
|
|
## 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, [], fn(entry, list) ->
|
|
if fun.(entry) do
|
|
[entry|list]
|
|
else
|
|
throw({ :enum_take, list })
|
|
end
|
|
end) |> :lists.reverse
|
|
catch
|
|
{ :enum_take, list } -> :lists.reverse(list)
|
|
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
|
|
Enumerable.reduce(collection, [], fn(entry, acc) ->
|
|
[entry|acc]
|
|
end) |> :lists.reverse
|
|
end
|
|
|
|
@doc """
|
|
Iterates the enumerable 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, { [], [] }, fn(entry, { acc, fun_acc }) ->
|
|
fun_entry = fun.(entry)
|
|
if :lists.member(fun_entry, fun_acc) do
|
|
{ acc, fun_acc }
|
|
else
|
|
{ [entry|acc], [fun_entry|fun_acc] }
|
|
end
|
|
end)
|
|
|
|
:lists.reverse(list)
|
|
end
|
|
|
|
@doc """
|
|
Zips corresponding elements from two collections into one list
|
|
of tuples. The number of elements in the resulting list is
|
|
dictated by the first enum. If the second list is shorter,
|
|
values are filled with `nil`.
|
|
|
|
## 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},{4,nil},{5,nil}]
|
|
|
|
"""
|
|
@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
|
|
list = to_list(coll1)
|
|
{ zipped, rest } = Enumerable.reduce(coll2, { [], list }, fn
|
|
entry, { acc, [h|t] } -> { [{h, entry}|acc], t }
|
|
_, { acc, [] } -> throw { :enum_zip, acc }
|
|
end)
|
|
|
|
list = :lists.foldl(fn(x, acc) -> [{ x, nil }|acc] end, zipped, rest)
|
|
:lists.reverse(list)
|
|
catch
|
|
{ :enum_zip, rest } -> :lists.reverse(rest)
|
|
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 false
|
|
def max(coll, fun) do
|
|
IO.write "Enum.max/2 is deprecated, please use Enum.max_by/2 instead\n#{Exception.format_stacktrace}"
|
|
max_by(coll, fun)
|
|
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
|
|
{ max, _ } = Enumerable.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)
|
|
|
|
max
|
|
end
|
|
|
|
def max_by([], _fun) do
|
|
raise Enum.EmptyError
|
|
end
|
|
|
|
def max_by(collection, fun) do
|
|
result = Enumerable.reduce(collection, :first, fn
|
|
entry, :first ->
|
|
{ :reduce, entry, fun.(entry) }
|
|
entry, { :reduce, _, fun_max } = old ->
|
|
fun_entry = fun.(entry)
|
|
if fun_entry > fun_max, do: { :reduce, entry, fun_entry }, else: old
|
|
end)
|
|
|
|
case result do
|
|
:first -> raise Enum.EmptyError
|
|
{ :reduce, entry, _ } -> entry
|
|
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 false
|
|
def min(coll, fun) do
|
|
IO.write "Enum.min/2 is deprecated, please use Enum.min_by/2 instead\n#{Exception.format_stacktrace}"
|
|
min_by(coll, fun)
|
|
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
|
|
{ min, _ } = Enumerable.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)
|
|
|
|
min
|
|
end
|
|
|
|
def min_by([], _fun) do
|
|
raise Enum.EmptyError
|
|
end
|
|
|
|
def min_by(collection, fun) do
|
|
result = Enumerable.reduce(collection, :first, fn
|
|
entry, :first ->
|
|
{ :reduce, entry, fun.(entry) }
|
|
entry, { :reduce, _, fun_min } = old ->
|
|
fun_entry = fun.(entry)
|
|
if fun_entry < fun_min, do: { :reduce, entry, fun_entry }, else: old
|
|
end)
|
|
|
|
case result do
|
|
:first -> raise Enum.EmptyError
|
|
{ :reduce, entry, _ } -> entry
|
|
end
|
|
end
|
|
|
|
@doc """
|
|
Returns the collection with each element wrapped in a tuple
|
|
along side 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
|
|
|
|
@doc """
|
|
Shortcut to `chunks(coll, n, n)`.
|
|
"""
|
|
@spec chunks(t, non_neg_integer) :: [list]
|
|
def chunks(coll, n), do: chunks(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.chunks([1, 2, 3, 4, 5, 6], 2)
|
|
[[1, 2], [3, 4], [5, 6]]
|
|
iex> Enum.chunks([1, 2, 3, 4, 5, 6], 3, 2)
|
|
[[1, 2, 3], [3, 4, 5]]
|
|
iex> Enum.chunks([1, 2, 3, 4, 5, 6], 3, 2, [7])
|
|
[[1, 2, 3], [3, 4, 5], [5, 6, 7]]
|
|
|
|
"""
|
|
@spec chunks(t, non_neg_integer, non_neg_integer) :: [list]
|
|
@spec chunks(t, non_neg_integer, non_neg_integer, list) :: [list]
|
|
def chunks(coll, n, step, pad // nil) when n > 0 and step > 0 do
|
|
{ acc, buffer, i } =
|
|
Enumerable.reduce(coll, { [], [], 0 }, fn
|
|
x, { acc, buffer, i } when i < n ->
|
|
chunks_n(acc, [x|buffer], i + 1, n, step)
|
|
x, { acc, buffer, i } when i < step ->
|
|
chunks_step(acc, [x|buffer], i + 1, step)
|
|
end)
|
|
|
|
if nil?(pad) do
|
|
:lists.reverse(acc)
|
|
else
|
|
buffer = :lists.reverse(buffer) ++ take(pad, n - i)
|
|
:lists.reverse([buffer|acc])
|
|
end
|
|
end
|
|
|
|
## Helpers
|
|
|
|
@compile { :inline, chunks_n: 5, chunks_step: 4 }
|
|
|
|
defp chunks_n(acc, buffer, i, n, step) when i == n do
|
|
acc = [:lists.reverse(buffer)|acc]
|
|
chunks_step(acc, buffer, i, step)
|
|
end
|
|
|
|
defp chunks_n(acc, buffer, i, _n, _step) do
|
|
{ acc, buffer, i }
|
|
end
|
|
|
|
defp chunks_step(acc, buffer, i, step) when i >= step do
|
|
left = i - step
|
|
{ acc, take(buffer, left), left }
|
|
end
|
|
|
|
defp chunks_step(acc, buffer, i, _step) do
|
|
{ acc, buffer, i }
|
|
end
|
|
|
|
defp iterate_and_count(collection, count) do
|
|
{ list, total_items } = do_iterate_and_count(collection)
|
|
{ list, Kernel.max(0, total_items - abs(count)) }
|
|
end
|
|
|
|
defp do_iterate_and_count(collection) when is_list(collection) do
|
|
{ collection, length(collection) }
|
|
end
|
|
|
|
defp do_iterate_and_count(collection) do
|
|
reducer = fn(x, acc) -> { x, acc + 1 } end
|
|
map_reduce(collection, 0, reducer)
|
|
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
|
|
|
|
## 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], other) do
|
|
{ h2, next2 } = do_zip_next(other)
|
|
[{ h1, h2 }|do_zip(next1, next2)]
|
|
end
|
|
|
|
defp do_zip([], _) do
|
|
[]
|
|
end
|
|
|
|
defp do_zip_next([h|t]), do: { h, t }
|
|
defp do_zip_next([]), do: { nil, [] }
|
|
end
|
|
|
|
defimpl Enumerable, for: List do
|
|
def reduce([h|t], acc, fun) do
|
|
reduce(t, fun.(h, acc), fun)
|
|
end
|
|
|
|
def reduce([], acc, _fun) do
|
|
acc
|
|
end
|
|
|
|
def member?([], _), do: false
|
|
def member?(list, value), do: :lists.member(value, list)
|
|
|
|
def count(list), do: length(list)
|
|
end
|
|
|
|
defimpl Enumerable, for: Function do
|
|
def reduce(function, acc, fun) do
|
|
function.(acc, fun)
|
|
end
|
|
|
|
def member?(function, value) do
|
|
function.(false, fn(entry, _) ->
|
|
if entry === value, do: throw(:function_member?), else: false
|
|
end)
|
|
catch
|
|
:function_member? -> true
|
|
end
|
|
|
|
def count(function) do
|
|
function.(0, fn(_, acc) -> acc + 1 end)
|
|
end
|
|
end
|