Packages
moar
2.1.0
5.0.0
4.3.0
4.2.0
4.1.0
4.0.0
3.2.0
3.1.0
3.0.0
2.7.0
2.6.0
2.5.0
2.4.2
2.4.1
2.4.0
2.3.0
2.2.0
2.1.0
2.0.1
2.0.0
1.64.0
1.63.1
1.63.0
1.62.0
1.61.2
1.61.1
1.61.0
1.60.0
1.59.2
1.59.1
1.59.0
1.58.0
1.57.0
1.56.2
1.56.1
1.56.0
1.55.0
1.54.0
1.53.0
1.52.1
1.52.0
1.51.0
1.50.0
1.49.0
1.48.0
1.47.0
1.46.0
1.45.1
1.45.0
1.44.0
1.43.0
1.42.0
1.41.0
1.40.0
1.39.0
1.38.0
1.37.0
1.36.0
1.35.0
1.34.0
1.33.0
1.32.0
1.31.0
1.30.0
1.29.0
1.28.0
1.27.0
1.26.0
1.25.0
1.24.1
1.24.0
1.23.0
1.22.0
1.21.0
1.20.0
1.19.3
1.19.2
1.19.1
1.19.0
1.18.1
1.18.0
1.17.0
1.16.0
1.15.0
1.14.0
1.13.1
1.13.0
1.12.0
1.11.0
1.10.0
1.9.0
1.8.0
1.7.0
1.6.0
1.5.0
1.4.0
1.3.0
1.2.0
1.1.0
1.0.0
0.1.0
A dependency-free utility library containing 100+ useful functions.
Current section
Files
Jump to
Current section
Files
lib/enum.ex
defmodule Moar.Enum do
# @related [test](/test/enum_test.exs)
@moduledoc "Enum-related functions."
@doc "Like `Enum.at/3` but raises if `index` is out of bounds."
@spec at!(Enum.t(), integer() | [integer()], any()) :: any()
def at!(enum, index, default \\ nil) when is_integer(index) do
if length(enum) < index + 1,
do: raise("Out of range: index #{index} of enum with length #{length(enum)}: #{inspect(enum)}"),
else: Enum.at(enum, index, default)
end
@doc "Removes nil elements from `enum`."
@spec compact(Enum.t()) :: Enum.t()
def compact(enum),
do: enum |> Enum.reject(&is_nil/1)
@doc "Removes blank elements (as determined by `Moar.Term.blank?1`) from `enum`."
@spec compact_blank(Enum.t()) :: Enum.t()
def compact_blank(enum),
do: enum |> Enum.reject(&Moar.Term.blank?/1)
@doc """
Returns the indices of `elements` in `enum`, using `fun` for comparisons (defaulting to `Kernel.==/2`)
```elixir
iex> Moar.Enum.find_indices(~w[apple banana cherry], ~w[cherry apple])
[2, 0]
iex> Moar.Enum.find_indices(~w[apple banana cherry], ~w[CHERRY APPLE], fn a, b ->
...> String.downcase(a) == String.downcase(b)
...> end)
[2, 0]
```
"""
@spec find_indices(Enum.t(), [any()], (any(), any() -> boolean())) :: [integer()]
def find_indices(enum, elements, fun \\ &Kernel.==/2),
do: Enum.map(elements, &Enum.find_index(enum, fn element -> fun.(element, &1) end))
@doc """
Returns the indices of `elements` in `enum`, raising if any member of `elements` is not found.
```elixir
iex> Moar.Enum.find_indices!(~w[apple banana cherry], ~w[cherry apple])
[2, 0]
iex> Moar.Enum.find_indices!(~w[apple banana], ~w[cherry apple])
** (RuntimeError) Element "cherry" not present in:
["apple", "banana"]
```
"""
@spec find_indices!(Enum.t(), [any()], (any(), any() -> boolean())) :: [integer()]
def find_indices!(enum, elements, fun \\ &Kernel.==/2) do
Enum.map(elements, fn element ->
Enum.find_index(enum, fn member ->
fun.(member, element)
end) || raise("Element #{inspect(element)} not present in:\n#{inspect(enum)}")
end)
end
@doc "Returns the first item of `enum`, or raises if it is empty."
@spec first!(Enum.t()) :: any()
def first!(enum),
do: Enum.at(enum, 0) || raise("Expected enumerable to have at least one item")
@doc """
Converts an enum into a map of maps indexed by the return value of `index_fun`.
See also the similar map-specific `Moar.Map.index_by/2`.
```elixir
iex> Moar.Enum.index_by([%{name: "Alice", tid: "alice"}, %{name: "Billy", tid: "billy"}], & &1.tid)
%{"alice" => %{name: "Alice", tid: "alice"}, "billy" => %{name: "Billy", tid: "billy"}}
```
"""
@spec index_by(Enum.t(), (any() -> any())) :: map()
def index_by(enum, index_fun) when is_function(index_fun),
do: Enum.reduce(enum, %{}, fn value, acc -> Moar.Map.put_new!(acc, index_fun.(value), value) end)
@doc "Like `Enum.into` but accepts `nil` as the first argument"
@spec into!(nil | Enum.t(), Enum.t()) :: Enum.t()
def into!(nil, enumerable), do: enumerable
def into!(%_{} = struct, enumerable), do: struct |> Map.from_struct() |> into!(enumerable)
def into!(other, enumerable), do: Enum.into(other, enumerable)
@doc """
Deprecated in favor of `map_or_keyword?/1`.
"""
@spec is_map_or_keyword(any()) :: boolean()
@deprecated "Use map_or_keyword?/1"
def is_map_or_keyword(value),
do: map_or_keyword?(value)
@doc """
Returns true if the value is a map or a keyword list. This uses standard Elixir functions for determining
if a term is a map or a keyword, and therefore counts an empty list as a keyword list. See also
`map_or_nonempty_keyword?/1`.
This cannot be used as a guard because it uses `Keyword.keyword?` under the hood. Also, because of that,
it might scan an entire list to see if it's a keyword list, so it might be expensive.
"""
@spec map_or_keyword?(any()) :: boolean()
def map_or_keyword?(value),
do: (is_map(value) && !is_struct(value)) || (is_list(value) && Keyword.keyword?(value))
@doc """
Deprecated in favor of `map_or_nonempty_keyword?/1`.
"""
@spec is_map_or_nonempty_keyword(any()) :: boolean()
@deprecated "Use map_or_nonempty_keyword?/1"
def is_map_or_nonempty_keyword(value),
do: is_map(value) || (is_list(value) && !Enum.empty?(value) && Keyword.keyword?(value))
@doc """
Like `map_or_keyword?/1` but returns false if the term is an empty list.
"""
@spec map_or_nonempty_keyword?(any()) :: boolean()
def map_or_nonempty_keyword?(value),
do: (is_map(value) && !is_struct(value)) || (is_list(value) && !Enum.empty?(value) && Keyword.keyword?(value))
@doc "Sorts `enum` case-insensitively. Uses `Enum.sort_by/3` under the hood."
@spec isort(Enum.t()) :: Enum.t()
def isort(enum),
do: enum |> isort_by(&to_string/1)
@doc "Sorts `enum` case-insensitively by `mapper` function. Uses `Enum.sort_by/3` under the hood."
@spec isort_by(Enum.t(), (any() -> any())) :: Enum.t()
def isort_by(enum, mapper),
do: enum |> Enum.sort_by(&(mapper.(&1) |> String.downcase()))
@doc """
Converts a list of lists to a list of maps with the given keys. The keys can be a list, or can be `:first_list`
which uses the first list in `list_of_lists` as the keys and the remaining lists in `list_of_lists` as the values.
```elixir
iex> Moar.Enum.lists_to_maps([[1, 2], [3, 4]], ["a", "b"])
[%{"a" => 1, "b" => 2}, %{"a" => 3, "b" => 4}]
iex> csv = [["a", "b"], [1, 2], [3, 4]]
iex> [headers | rows] = csv
iex> Moar.Enum.lists_to_maps(rows, headers)
[%{"a" => 1, "b" => 2}, %{"a" => 3, "b" => 4}]
iex> csv = [["a", "b"], [1, 2], [3, 4]]
iex> Moar.Enum.lists_to_maps(csv, :first_list)
[%{"a" => 1, "b" => 2}, %{"a" => 3, "b" => 4}]
```
"""
@spec lists_to_maps(list(list(any())), list(any()) | :first_list) :: list(map())
def lists_to_maps([first_list | remaining_lists], :first_list),
do: lists_to_maps(remaining_lists, first_list)
def lists_to_maps(list_of_lists, keys),
do: Enum.map(list_of_lists, &Map.new(Enum.zip([keys, &1])))
@doc """
Returns a list of elements at the given indices, in the given order. If `:all` is given instead of a list of indices,
the entire enum is returned.
```elixir
iex> Moar.Enum.take_at(["A", "B", "C"], [0, 2])
["A", "C"]
iex> Moar.Enum.take_at(["A", "B", "C"], [2, 0])
["C", "A"]
iex> Moar.Enum.take_at(["A", "B", "C"], :all)
["A", "B", "C"]
```
"""
@spec take_at(Enum.t(), integer() | [integer()] | :all) :: any()
def take_at(enum, :all), do: enum
def take_at(enum, list) when is_list(list), do: Enum.map(list, &Enum.at(enum, &1))
@doc """
Returns `:test_id` fields from `enumerable`.
This unusual function exists because the authors of Moar use test_ids extensively in tests.
See also `tids/2`.
"""
@type test_ids_opts() :: {:sorted, boolean()}
@spec test_ids(Enum.t(), [test_ids_opts()]) :: list()
def test_ids(enumerable, opts \\ []) do
test_ids = enumerable |> Enum.map(& &1.test_id)
if Keyword.get(opts, :sorted),
do: test_ids |> Enum.sort(),
else: test_ids
end
@doc """
Returns `:tid` fields from `enumerable`.
This unusual function exists because the authors of Moar use tids (test IDs) extensively in tests.
See also `test_ids/2`.
"""
@type tids_opts() :: {:sorted, boolean()}
@spec tids(Enum.t(), [tids_opts()]) :: list()
def tids(enumerable, opts \\ []) do
tids = enumerable |> Enum.map(& &1.tid)
if Keyword.get(opts, :sorted),
do: tids |> Enum.sort(),
else: tids
end
end