Algo.List (Algo v0.1.0)

Copy Markdown View Source

Utility functions for lists.

Summary

Functions

Chunks adjacent elements while a predicate holds for each neighbouring pair.

Returns elements from the first enumerable whose derived keys are absent from the second enumerable.

Returns elements from the first enumerable that have no comparator match in the second enumerable.

Returns the Cartesian product of two lists.

Returns all permutations of a list.

Returns every subsequence of a list.

Returns elements whose derived keys are present in only one enumerable.

Returns elements from both enumerables that have no comparator match in the other enumerable.

Returns all unique unordered pairs from a list.

Builds a map by indexing each element with a key function.

Returns all matching pairs from two enumerables.

Returns elements from the first enumerable whose derived keys are present in the second enumerable.

Returns elements from the first enumerable that have a comparator match in the second enumerable.

Alternates elements from two lists.

Maps a list while threading an accumulator from left to right.

Maps a list while threading an accumulator from right to left.

Moves the element at one index to another index.

Maps each element into one of two partitions.

Reduces elements into groups selected by a key function.

Splits a list before every element that satisfies a predicate.

Transposes a rectangular list of rows.

Returns unique elements according to a comparator.

Expands maps by replacing a list field with each of its values.

Functions

chunk_while_adjacent(list, fun)

@spec chunk_while_adjacent(list(), (any(), any() -> as_boolean(term()))) :: [list()]

Chunks adjacent elements while a predicate holds for each neighbouring pair.

The predicate receives the previous element and the current element. A truthy result keeps the current element in the active chunk; a falsy result starts a new chunk.

Examples

iex> Algo.List.chunk_while_adjacent([1, 2, 3, 1, 2], fn left, right -> right == left + 1 end)
[[1, 2, 3], [1, 2]]

difference_by(left, right, key_fun)

@spec difference_by(Enumerable.t(), Enumerable.t(), (any() -> any())) :: list()

Returns elements from the first enumerable whose derived keys are absent from the second enumerable.

Duplicate elements from the first enumerable are preserved when their key is absent from the second enumerable.

Examples

iex> Algo.List.difference_by([%{id: 1}, %{id: 2}], [%{id: 2}], & &1.id)
[%{id: 1}]

difference_with(left, right, fun)

@spec difference_with(Enumerable.t(), Enumerable.t(), (any(), any() ->
                                                   as_boolean(term()))) ::
  list()

Returns elements from the first enumerable that have no comparator match in the second enumerable.

The comparator receives {left_element, right_element} and a truthy return value means the elements match.

Examples

iex> Algo.List.difference_with(["a", "bb"], ["c"], Algo.eq_by?(&String.length/1))
["bb"]

get_cartesian_product(list1, list2, output_as)

@spec get_cartesian_product(list(), list(), :lists | :tuples) ::
  [list()] | [{any(), any()}]

Returns the Cartesian product of two lists.

Pass :lists to return each pair as a two-item list, or :tuples to return each pair as a tuple.

Examples

iex> Algo.List.get_cartesian_product([1, 2], [:a, :b], :tuples)
[{1, :a}, {1, :b}, {2, :a}, {2, :b}]

iex> Algo.List.get_cartesian_product([1, 2], [:a], :lists)
[[1, :a], [2, :a]]

get_permutations(list)

@spec get_permutations(list()) :: [list()]

Returns all permutations of a list.

Duplicate input values may produce duplicate permutations.

Examples

iex> Algo.List.get_permutations([1, 2, 3])
[[1, 2, 3], [1, 3, 2], [2, 1, 3], [2, 3, 1], [3, 1, 2], [3, 2, 1]]

iex> Algo.List.get_permutations([])
[[]]

get_subsequences(list)

@spec get_subsequences(list()) :: [list()]

Returns every subsequence of a list.

The empty list is included. Subsequence order follows the recursive generation order used by the function.

Examples

iex> Algo.List.get_subsequences([1, 2])
[[], [1], [2], [1, 2]]

iex> Algo.List.get_subsequences([])
[[]]

get_symmetric_difference_by(left, right, key_fun)

@spec get_symmetric_difference_by(Enumerable.t(), Enumerable.t(), (any() -> any())) ::
  list()

Returns elements whose derived keys are present in only one enumerable.

Items from the first enumerable are returned before items from the second. Duplicates are preserved for keys that are exclusive to their side.

Examples

iex> Algo.List.get_symmetric_difference_by([%{id: 1}, %{id: 2}], [%{id: 2}, %{id: 3}], & &1.id)
[%{id: 1}, %{id: 3}]

get_symmetric_difference_with(left, right, fun)

@spec get_symmetric_difference_with(Enumerable.t(), Enumerable.t(), (any(), any() ->
                                                                 as_boolean(
                                                                   term()
                                                                 ))) ::
  list()

Returns elements from both enumerables that have no comparator match in the other enumerable.

Items from the first enumerable are returned before items from the second. For first-side items, the comparator receives {left_element, right_element}; for second-side items, it receives {right_element, left_element}.

Examples

iex> Algo.List.get_symmetric_difference_with(["a", "bb"], ["c", "ddd"], Algo.eq_by?(&String.length/1))
["bb", "ddd"]

get_unique_pairs(list, output_as)

@spec get_unique_pairs(list(), :lists | :tuples) :: [list()] | [{any(), any()}]

Returns all unique unordered pairs from a list.

Pass :lists to return pairs as two-item lists, or :tuples to return pairs as tuples.

Examples

iex> Algo.List.get_unique_pairs([1, 2, 3], :lists)
[[1, 2], [1, 3], [2, 3]]

iex> Algo.List.get_unique_pairs([1, 2], :tuples)
[{1, 2}]

iex> Algo.List.get_unique_pairs([1], :lists)
[]

index_by(enumerable, fun)

@spec index_by(Enumerable.t(), (any() -> any())) :: map()

Builds a map by indexing each element with a key function.

If multiple elements produce the same key, the later element wins.

Examples

iex> Algo.List.index_by([%{id: 1, name: "old"}, %{id: 1, name: "new"}], & &1.id)
%{1 => %{id: 1, name: "new"}}

inner_join(left, right, fun)

@spec inner_join(Enumerable.t(), Enumerable.t(), (any(), any() -> as_boolean(term()))) ::
  [
    {any(), any()}
  ]

Returns all matching pairs from two enumerables.

The predicate receives a left element and a right element. Every truthy match is emitted as {left_element, right_element}.

Examples

iex> Algo.List.inner_join([%{id: 1}], [%{user_id: 1}, %{user_id: 2}], fn left, right -> left.id == right.user_id end)
[{%{id: 1}, %{user_id: 1}}]

intersect_by(left, right, key_fun)

@spec intersect_by(Enumerable.t(), Enumerable.t(), (any() -> any())) :: list()

Returns elements from the first enumerable whose derived keys are present in the second enumerable.

Input order and duplicate elements from the first enumerable are preserved.

Examples

iex> Algo.List.intersect_by([%{id: 1}, %{id: 2}, %{id: 2}], [%{id: 2}], & &1.id)
[%{id: 2}, %{id: 2}]

intersect_with(left, right, fun)

@spec intersect_with(Enumerable.t(), Enumerable.t(), (any(), any() ->
                                                  as_boolean(term()))) :: list()

Returns elements from the first enumerable that have a comparator match in the second enumerable.

The comparator receives {left_element, right_element} and a truthy return value means the elements match.

Examples

iex> Algo.List.intersect_with(["a", "bb"], ["c"], Algo.eq_by?(&String.length/1))
["a"]

interweave(list1, list2)

@spec interweave(list(), list()) :: list()

Alternates elements from two lists.

When one list is longer, the remaining elements from that list are appended to the result.

Examples

iex> Algo.List.interweave([1, 2, 3], [:a, :b])
[1, :a, 2, :b, 3]

iex> Algo.List.interweave([], [:a, :b])
[:a, :b]

map_accum(list, acc, fun)

@spec map_accum(list(), any(), (any(), any() -> {any(), any()})) :: {list(), any()}

Maps a list while threading an accumulator from left to right.

The function receives each element and the current accumulator, and must return {mapped_element, next_accumulator}.

Examples

iex> Algo.List.map_accum([1, 2, 3], 0, fn x, sum -> {x * 2, sum + x} end)
{[2, 4, 6], 6}

map_accum_right(list, acc, fun)

@spec map_accum_right(list(), any(), (any(), any() -> {any(), any()})) ::
  {list(), any()}

Maps a list while threading an accumulator from right to left.

The function receives each element and the current accumulator, and must return {mapped_element, next_accumulator}. The returned mapped list keeps the input order.

Examples

iex> Algo.List.map_accum_right([1, 2, 3], 0, fn x, sum -> {x + sum, sum + x} end)
{[6, 5, 3], 6}

move_at(list, from_index, to_index)

@spec move_at(list(), integer(), integer()) :: list()

Moves the element at one index to another index.

Indexes are zero-based. Negative indexes count back from the end, like Enum.at/2. Both indexes must point at existing elements; otherwise ArgumentError is raised.

Examples

iex> Algo.List.move_at([:a, :b, :c, :d], 0, 2)
[:b, :c, :a, :d]

iex> Algo.List.move_at([:a, :b, :c], -1, 0)
[:c, :a, :b]

partition_map(enumerable, fun)

@spec partition_map(Enumerable.t(), (any() -> {:left, any()} | {:right, any()})) ::
  {list(), list()}

Maps each element into one of two partitions.

The mapping function must return {:left, value} or {:right, value}. The result is {left_values, right_values} with input order preserved in each partition.

Examples

iex> Algo.List.partition_map([1, 2, 3], fn x -> if rem(x, 2) == 0, do: {:right, x * 10}, else: {:left, x} end)
{[1, 3], [20]}

reduce_by(enumerable, key_fun, initial_acc, reduce_fun)

@spec reduce_by(Enumerable.t(), (any() -> any()), any(), (any(), any() -> any())) ::
  map()

Reduces elements into groups selected by a key function.

Each new key starts with initial_acc. The reducer receives the element and the current accumulator for that key.

Examples

iex> Algo.List.reduce_by(["a", "bb", "c"], &String.length/1, 0, fn _value, count -> count + 1 end)
%{1 => 2, 2 => 1}

split_whenever(list, fun)

@spec split_whenever(list(), (any() -> as_boolean(term()))) :: [list()]

Splits a list before every element that satisfies a predicate.

Matching elements are retained as the first element of their new chunk. Empty chunks are not emitted.

Examples

iex> Algo.List.split_whenever([1, 2, 3, 2, 4], &(&1 == 2))
[[1], [2, 3], [2, 4]]

iex> Algo.List.split_whenever([:a, :b], fn _ -> false end)
[[:a, :b]]

transpose(rows)

@spec transpose([list()]) :: [list()]

Transposes a rectangular list of rows.

All rows must be lists with the same length. An empty list of rows returns an empty list. If rows are not of equal lengths, the transposition stops at the end of the shortest row, matching the behaviour of Enum.zip.

Examples

iex> Algo.List.transpose([[1, 2, 3], [:a, :b, :c]])
[[1, :a], [2, :b], [3, :c]]

unique_with(enumerable, fun)

@spec unique_with(Enumerable.t(), (any(), any() -> as_boolean(term()))) :: list()

Returns unique elements according to a comparator.

The first occurrence is kept. The comparator receives an already kept element and the candidate element; a truthy return value means the two elements are considered the same.

Examples

iex> Algo.List.unique_with(["a", "bb", "c"], Algo.eq_by?(&String.length/1))
["a", "bb"]

unwind(enumerable, field)

@spec unwind(Enumerable.t(), any()) :: list()

Expands maps by replacing a list field with each of its values.

Items with a list field emit one item per field value. An empty list emits no items. Missing fields and non-list fields are left unchanged.

Examples

iex> Algo.List.unwind([%{id: 1, tags: [:a, :b]}, %{id: 2, tags: []}], :tags)
[%{id: 1, tags: :a}, %{id: 1, tags: :b}]

iex> Algo.List.unwind([%{id: 1}, %{id: 2, tags: :a}], :tags)
[%{id: 1}, %{id: 2, tags: :a}]