defmodule Moar.Tuple do # @related [test](/test/tuple_test.exs) @moduledoc "Tuple-related functions." @doc """ Converts a list of tuples to a single tuple whose first element is the first element of each tuple in the list (which must all be the same), and whose second element is a list containing the second elements of each tuple in the list. Raises if the list contains tuples whose first elements are not all the same. ```elixir iex> Moar.Tuple.from_list!([{:ok, :a}, {:ok, :b}]) {:ok, [:a, :b]} iex> Moar.Tuple.from_list!([{:a, 1}, {:a, 2}, {:a, 3}]) {:a, [1, 2, 3]} iex> Moar.Tuple.from_list!([{:a, 1}, {:b, 2}, {:a, 3}]) ** (RuntimeError) Expected all items in the list to have have the same first element, but got: [:a, :b] ``` """ @spec from_list!([any()]) :: {any(), [any()]} def from_list!(list) do {keys, values} = Enum.unzip(list) case Enum.uniq(keys) do [key] -> {key, values} keys -> raise "Expected all items in the list to have have the same first element, but got: #{inspect(keys)}" end end @doc """ Reduces a list of tuples to map where values are consolidated by the first element of each input tuple. Optionally accepts a list of default keys, whose values will be `[]` if not found in the input. ```elixir iex> Moar.Tuple.reduce([{:ok, 1}, {:ok, 2}]) %{ok: [1, 2]} iex> Moar.Tuple.reduce([{:ok, 1}, {:ok, 2}, {:error, 3}, {:ok, 4}]) %{ok: [1, 2, 4], error: [3]} iex> Moar.Tuple.reduce([{:ok, 1}, {:ok, 2}], [:ok, :warning, :error]) %{ok: [1, 2], warning: [], error: []} ``` """ @spec reduce([any()], [any()]) :: map() def reduce(list, defaults \\ []) do reduced = list |> Enum.reduce(%{}, fn {k, v}, acc -> Map.update(acc, k, [v], fn list -> [v | list] end) end) |> Map.new(fn {k, v} -> {k, Enum.reverse(v)} end) Enum.reduce(defaults, reduced, fn default, acc -> Map.put_new(acc, default, []) end) end end