# `Algo.List`
[🔗](https://github.com/alexkorban/algo/blob/main/lib/algo/list.ex#L1)

Utility functions for lists.

# `chunk_while_adjacent`

```elixir
@spec chunk_while_adjacent(list(), (any(), any() -&gt; 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`

```elixir
@spec difference_by(Enumerable.t(), Enumerable.t(), (any() -&gt; 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`

```elixir
@spec difference_with(Enumerable.t(), Enumerable.t(), (any(), any() -&gt;
                                                   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`

```elixir
@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`

```elixir
@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`

```elixir
@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`

```elixir
@spec get_symmetric_difference_by(Enumerable.t(), Enumerable.t(), (any() -&gt; 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`

```elixir
@spec get_symmetric_difference_with(Enumerable.t(), Enumerable.t(), (any(), any() -&gt;
                                                                 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`

```elixir
@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`

```elixir
@spec index_by(Enumerable.t(), (any() -&gt; 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`

```elixir
@spec inner_join(Enumerable.t(), Enumerable.t(), (any(), any() -&gt; 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`

```elixir
@spec intersect_by(Enumerable.t(), Enumerable.t(), (any() -&gt; 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`

```elixir
@spec intersect_with(Enumerable.t(), Enumerable.t(), (any(), any() -&gt;
                                                  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`

```elixir
@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`

```elixir
@spec map_accum(list(), any(), (any(), any() -&gt; {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`

```elixir
@spec map_accum_right(list(), any(), (any(), any() -&gt; {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`

```elixir
@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`

```elixir
@spec partition_map(Enumerable.t(), (any() -&gt; {: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`

```elixir
@spec reduce_by(Enumerable.t(), (any() -&gt; any()), any(), (any(), any() -&gt; 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`

```elixir
@spec split_whenever(list(), (any() -&gt; 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`

```elixir
@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`

```elixir
@spec unique_with(Enumerable.t(), (any(), any() -&gt; 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`

```elixir
@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}]

---

*Consult [api-reference.md](api-reference.md) for complete listing*
