# SPDX-License-Identifier: Apache-2.0
# SPDX-FileCopyrightText: 2021 The Elixir Team
# SPDX-FileCopyrightText: 2012 Plataformatec

defprotocol Enumerable do
  @moduledoc """
  Enumerable protocol used by `Enum` and `Stream` modules.

  When you invoke a function in the `Enum` module, the first argument
  is usually a collection that must implement this protocol.
  For example, the expression `Enum.map([1, 2, 3], &(&1 * 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 consuming the element with an
  accumulated list.

  Internally, `Enum.map/2` is implemented as follows:

      def map(enumerable, fun) do
        reducer = fn x, acc -> {:cont, [fun.(x) | acc]} end
        Enumerable.reduce(enumerable, {:cont, []}, reducer) |> elem(1) |> :lists.reverse()
      end

  Note that the user-supplied function is wrapped into a `t:reducer/0` function.
  The `t:reducer/0` function must return a tagged tuple after each step,
  as described in the `t:acc/0` type. At the end, `Enumerable.reduce/3`
  returns `t:result/0`.

  This protocol uses tagged tuples to exchange information between the
  reducer function and the data type that implements the protocol. This
  allows enumeration of resources, such as files, to be done efficiently
  while also guaranteeing the resource will be closed at the end of the
  enumeration. This protocol also allows suspension of the enumeration,
  which is useful when interleaving between many enumerables is required
  (as in the `zip/1` and `zip/2` functions).

  This protocol requires four functions to be implemented, `reduce/3`,
  `count/1`, `member?/2`, and `slice/1`. The core of the protocol is the
  `reduce/3` function. All other functions exist as optimizations paths
  for data structures that can implement certain properties in better
  than linear time.

  ## Default implementation for lists

  Sometimes you may want to implement this protocol for a list contained
  in struct. This can be done by delegating to the `Enumerable.List` module
  in the `reduce/3` implementation and providing a straight-forward
  implementation for the remaining ones:

      defimpl Enumerable, for: CustomStruct do
        def count(struct), do: {:ok, length(struct.items)}
        def member?(struct, value), do: {:ok, value in struct.items}
        def slice(struct), do: {:error, __MODULE__}
        def reduce(struct, acc, fun), do: Enumerable.List.reduce(struct.items, acc, fun)
      end
  """

  @typedoc """
  An enumerable of elements of type `element`.

  This type is equivalent to `t:t/0` but is especially useful for documentation.

  For example, imagine you define a function that expects an enumerable of
  integers and returns an enumerable of strings:

      @spec integers_to_strings(Enumerable.t(integer())) :: Enumerable.t(String.t())
      def integers_to_strings(integers) do
        Stream.map(integers, &Integer.to_string/1)
      end

  """
  @typedoc since: "1.14.0"
  @type t(_element) :: t()

  @typedoc """
  The accumulator value for each step.

  It must be a tagged tuple with one of the following "tags":

    * `:cont`    - the enumeration should continue
    * `:halt`    - the enumeration should halt immediately
    * `:suspend` - the enumeration should be suspended immediately

  Depending on the accumulator value, the result returned by
  `Enumerable.reduce/3` will change. Please check the `t:result/0`
  type documentation for more information.

  In case a `t:reducer/0` function returns a `:suspend` accumulator,
  it must be explicitly handled by the caller and never leak.
  """
  @type acc :: {:cont, term} | {:halt, term} | {:suspend, term}

  @typedoc """
  The reducer function.

  Should be called with the `enumerable` element and the
  accumulator contents.

  Returns the accumulator for the next enumeration step.
  """
  @type reducer :: (element :: term, element_acc :: term -> acc)

  @typedoc """
  The result of the reduce operation.

  It may be *done* when the enumeration is finished by reaching
  its end, or *halted*/*suspended* when the enumeration was halted
  or suspended by the tagged accumulator.

  In case the tagged `:halt` accumulator is given, the `:halted` tuple
  with the accumulator must be returned. Functions like `Enum.take_while/2`
  use `:halt` underneath and can be used to test halting enumerables.

  In case the tagged `:suspend` accumulator is given, the caller must
  return the `:suspended` tuple with the accumulator and a continuation.
  The caller is then responsible of managing the continuation and the
  caller must always call the continuation, eventually halting or continuing
  until the end. `Enum.zip/2` uses suspension, so it can be used to test
  whether your implementation handles suspension correctly. You can also use
  `Stream.zip/2` with `Enum.take_while/2` to test the combination of
  `:suspend` with `:halt`.
  """
  @type result ::
          {:done, term}
          | {:halted, term}
          | {:suspended, term, continuation}

  @typedoc """
  A partially applied reduce function.

  The continuation is the closure returned as a result when
  the enumeration is suspended. When invoked, it expects
  a new accumulator and it returns the result.

  A continuation can be trivially implemented as long as the reduce
  function is defined in a tail recursive fashion. If the function
  is tail recursive, all the state is passed as arguments, so
  the continuation is the reducing function partially applied.
  """
  @type continuation :: (acc -> result)

  @typedoc """
  A slicing function that receives the initial position,
  the number of elements in the slice, and the step.

  The `start` position is a number `>= 0` and guaranteed to
  exist in the `enumerable`. The length is a number `>= 1`
  in a way that `start + length * step <= count`, where
  `count` is the maximum amount of elements in the enumerable.

  The function should return a non empty list where
  the amount of elements is equal to `length`.
  """
  @type slicing_fun ::
          (start :: non_neg_integer, length :: pos_integer, step :: pos_integer -> [term()])

  @typedoc """
  Receives an enumerable and returns a list.
  """
  @type to_list_fun :: (t -> [term()])

  @doc """
  Reduces the `enumerable` into an element.

  Most of the operations in `Enum` are implemented in terms of reduce.
  This function should apply the given `t:reducer/0` function to each
  element in the `enumerable` and proceed as expected by the returned
  accumulator.

  See the documentation of the types `t:result/0` and `t:acc/0` for
  more information.

  ## Examples

  As an example, here is the implementation of `reduce` for lists:

      def reduce(_list, {:halt, acc}, _fun), do: {:halted, acc}
      def reduce(list, {:suspend, acc}, fun), do: {:suspended, acc, &reduce(list, &1, fun)}
      def reduce([], {:cont, acc}, _fun), do: {:done, acc}
      def reduce([head | tail], {:cont, acc}, fun), do: reduce(tail, fun.(head, acc), fun)

  """
  @spec reduce(t, acc, reducer) :: result
  def reduce(enumerable, acc, fun)

  @doc """
  Retrieves the number of elements in the `enumerable`.

  It should return `{:ok, count}` if you can count the number of elements
  in `enumerable` in a faster way than fully traversing it.

  Otherwise it should return `{:error, __MODULE__}` and a default algorithm
  built on top of `reduce/3` that runs in linear time will be used.
  """
  @spec count(t) :: {:ok, non_neg_integer} | {:error, module}
  def count(enumerable)

  @doc """
  Checks if an `element` exists within the `enumerable`.

  It should return `{:ok, boolean}` if you can check the membership of a
  given element in `enumerable` with `===/2` without traversing the whole
  of it.

  Otherwise it should return `{:error, __MODULE__}` and a default algorithm
  built on top of `reduce/3` that runs in linear time will be used.

  When called outside guards, the [`in`](`in/2`) and [`not in`](`in/2`)
  operators work by using this function.
  """
  @spec member?(t, term) :: {:ok, boolean} | {:error, module}
  def member?(enumerable, element)

  @doc """
  Returns a function that slices the data structure contiguously.

  It should return either:

    * `{:ok, size, slicing_fun}` - if the `enumerable` has a known
      bound and can access a position in the `enumerable` without
      traversing all previous elements. The `slicing_fun` will receive
      a `start` position, the `amount` of elements to fetch, and a
      `step`.

    * `{:ok, size, to_list_fun}` - if the `enumerable` has a known bound
      and can access a position in the `enumerable` by first converting
      it to a list via `to_list_fun`.

    * `{:error, __MODULE__}` - the enumerable cannot be sliced efficiently
      and a default algorithm built on top of `reduce/3` that runs in
      linear time will be used.

  ## Differences to `count/1`

  The `size` value returned by this function is used for boundary checks,
  therefore it is extremely important that this function only returns `:ok`
  if retrieving the `size` of the `enumerable` is cheap, fast, and takes
  constant time. Otherwise the simplest of operations, such as
  `Enum.at(enumerable, 0)`, will become too expensive.

  On the other hand, the `count/1` function in this protocol should be
  implemented whenever you can count the number of elements in the collection
  without traversing it.
  """
  @spec slice(t) ::
          {:ok, size :: non_neg_integer(), slicing_fun() | to_list_fun()}
          | {:error, module()}
  def slice(enumerable)
end

defmodule Enum do
  import Kernel, except: [max: 2, min: 2]

  @moduledoc """
  Functions for working with collections (known as enumerables).

  In Elixir, an enumerable is any data type that implements the
  `Enumerable` protocol. `List`s (`[1, 2, 3]`), `Map`s (`%{foo: 1, bar: 2}`)
  and `Range`s (`1..3`) are common data types used as enumerables:

      iex> Enum.map([1, 2, 3], fn x -> x * 2 end)
      [2, 4, 6]

      iex> Enum.sum([1, 2, 3])
      6

      iex> Enum.map(1..3, fn x -> x * 2 end)
      [2, 4, 6]

      iex> Enum.sum(1..3)
      6

      iex> map = %{"a" => 1, "b" => 2}
      iex> Enum.map(map, fn {k, v} -> {k, v * 2} end)
      [{"a", 2}, {"b", 4}]

  Many other enumerables exist in the language, such as `MapSet`s
  and the data type returned by `File.stream!/3` which allows a file to be
  traversed as if it was an enumerable.

  For a general overview of all functions in the `Enum` module, see
  [the `Enum` cheatsheet](enum-cheat.cheatmd).

  The functions in this module work in linear time. This means that, the
  time it takes to perform an operation grows at the same rate as the length
  of the enumerable. This is expected on operations such as `Enum.map/2`.
  After all, if we want to traverse every element on a list, the longer the
  list, the more elements we need to traverse, and the longer it will take.

  This linear behavior should also be expected on operations like `count/1`,
  `member?/2`, `at/2` and similar. While Elixir does allow data types to
  provide performant variants for such operations, you should not expect it
  to always be available, since the `Enum` module is meant to work with a
  large variety of data types and not all data types can provide optimized
  behavior.

  Finally, note the functions in the `Enum` module are eager: they will
  traverse the enumerable as soon as they are invoked. This is particularly
  dangerous when working with infinite enumerables. In such cases, you should
  use the `Stream` module, which allows you to lazily express computations,
  without traversing collections, and work with possibly infinite collections.
  See the `Stream` module for examples and documentation.
  """

  @compile :inline_list_funcs

  @type t :: Enumerable.t()
  @type acc :: any
  @type element :: any

  @typedoc "Zero-based index. It can also be a negative integer."
  @type index :: integer

  @type default :: any

  require Stream.Reducers, as: R

  defmacrop skip(acc) do
    acc
  end

  defmacrop next(_, entry, acc) do
    quote(do: [unquote(entry) | unquote(acc)])
  end

  defmacrop acc(head, state, _) do
    quote(do: {unquote(head), unquote(state)})
  end

  defmacrop next_with_acc(_, entry, head, state, _) do
    quote do
      {[unquote(entry) | unquote(head)], unquote(state)}
    end
  end

  @doc """
  Returns `true` if all elements in `enumerable` are truthy.

  When an element has a falsy value (`false` or `nil`) iteration stops immediately
  and `false` is returned. In all other cases `true` is returned.

  ## Examples

      iex> Enum.all?([1, 2, 3])
      true

      iex> Enum.all?([1, nil, 3])
      false

      iex> Enum.all?([])
      true

  """
  @spec all?(t) :: boolean
  def all?(enumerable) when is_list(enumerable) do
    all_list(enumerable)
  end

  def all?(enumerable) do
    Enumerable.reduce(enumerable, {:cont, true}, fn entry, _ ->
      if entry, do: {:cont, true}, else: {:halt, false}
    end)
    |> elem(1)
  end

  @doc """
  Returns `true` if `fun.(element)` is truthy for all elements in `enumerable`.

  Iterates over `enumerable` and invokes `fun` on each element. If `fun` ever
  returns a falsy value (`false` or `nil`), iteration stops immediately and
  `false` is returned. Otherwise, `true` is returned.

  ## 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

      iex> Enum.all?([], fn _ -> nil end)
      true

  As the last example shows, `Enum.all?/2` returns `true` if `enumerable` is
  empty, regardless of `fun`. In an empty enumerable there is no element for
  which `fun` returns a falsy value, so the result must be `true`. This is a
  well-defined logical argument for empty collections.

  """
  @spec all?(t, (element -> as_boolean(term))) :: boolean
  def all?(enumerable, fun) when is_list(enumerable) do
    predicate_list(enumerable, true, fun)
  end

  def all?(first..last//step, fun) do
    predicate_range(first, last, step, true, fun)
  end

  def all?(enumerable, fun) do
    Enumerable.reduce(enumerable, {:cont, true}, fn entry, _ ->
      if fun.(entry), do: {:cont, true}, else: {:halt, false}
    end)
    |> elem(1)
  end

  @doc """
  Returns `true` if at least one element in `enumerable` is truthy.

  When an element has a truthy value (neither `false` nor `nil`) iteration stops
  immediately and `true` is returned. In all other cases `false` is returned.

  ## Examples

      iex> Enum.any?([false, false, false])
      false

      iex> Enum.any?([false, true, false])
      true

      iex> Enum.any?([])
      false

  """
  @spec any?(t) :: boolean
  def any?(enumerable) when is_list(enumerable) do
    any_list(enumerable)
  end

  def any?(enumerable) do
    Enumerable.reduce(enumerable, {:cont, false}, fn entry, _ ->
      if entry, do: {:halt, true}, else: {:cont, false}
    end)
    |> elem(1)
  end

  @doc """
  Returns `true` if `fun.(element)` is truthy for at least one element in `enumerable`.

  Iterates over the `enumerable` and invokes `fun` on each element. When an invocation
  of `fun` returns a truthy value (neither `false` nor `nil`) iteration stops
  immediately and `true` is returned. In all other cases `false` is returned.

  ## 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

      iex> Enum.any?([], fn x -> x > 0 end)
      false

  """
  @spec any?(t, (element -> as_boolean(term))) :: boolean
  def any?(enumerable, fun) when is_list(enumerable) do
    predicate_list(enumerable, false, fun)
  end

  def any?(first..last//step, fun) do
    predicate_range(first, last, step, false, fun)
  end

  def any?(enumerable, fun) do
    Enumerable.reduce(enumerable, {:cont, false}, fn entry, _ ->
      if fun.(entry), do: {:halt, true}, else: {:cont, false}
    end)
    |> elem(1)
  end

  @doc """
  Finds the element at the given `index` (zero-based).

  Returns `default` if `index` is out of bounds.

  A negative `index` can be passed, which means the `enumerable` is
  enumerated once and the `index` is counted from the end (for example,
  `-1` finds the last element).

  ## 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, default) :: element | default
  def at(enumerable, index, default \\ nil) when is_integer(index) do
    case slice_forward(enumerable, index, 1, 1) do
      [value] -> value
      [] -> default
    end
  end

  @doc false
  @deprecated "Use Enum.chunk_every/2 instead"
  def chunk(enumerable, count), do: chunk(enumerable, count, count, nil)

  @doc false
  @deprecated "Use Enum.chunk_every/3 instead"
  def chunk(enum, n, step) do
    chunk_every(enum, n, step, :discard)
  end

  @doc false
  @deprecated "Use Enum.chunk_every/4 instead"
  def chunk(enumerable, count, step, leftover) do
    chunk_every(enumerable, count, step, leftover || :discard)
  end

  @doc """
  Shortcut to `chunk_every(enumerable, count, count)`.
  """
  @doc since: "1.5.0"
  @spec chunk_every(t, pos_integer) :: [list]
  def chunk_every(enumerable, count), do: chunk_every(enumerable, count, count, [])

  @doc """
  Returns list of lists containing `count` elements each, where
  each new chunk starts `step` elements into the `enumerable`.

  `step` is optional and, if not passed, defaults to `count`, i.e.
  chunks do not overlap. Chunking will stop as soon as the collection
  ends or when we emit an incomplete chunk.

  If the last chunk does not have `count` elements to fill the chunk,
  elements are taken from `leftover` to fill in the chunk. If `leftover`
  does not have enough elements to fill the chunk, then a partial chunk
  is returned with less than `count` elements.

  If `:discard` is given in `leftover`, the last chunk is discarded
  unless it has exactly `count` elements.

  ## Examples

      iex> Enum.chunk_every([1, 2, 3, 4, 5, 6], 2)
      [[1, 2], [3, 4], [5, 6]]

      iex> Enum.chunk_every([1, 2, 3, 4, 5, 6], 3, 2, :discard)
      [[1, 2, 3], [3, 4, 5]]

      iex> Enum.chunk_every([1, 2, 3, 4, 5, 6], 3, 2, [7])
      [[1, 2, 3], [3, 4, 5], [5, 6, 7]]

      iex> Enum.chunk_every([1, 2, 3, 4], 3, 3, [])
      [[1, 2, 3], [4]]

      iex> Enum.chunk_every([1, 2, 3, 4], 10)
      [[1, 2, 3, 4]]

      iex> Enum.chunk_every([1, 2, 3, 4, 5], 2, 3, [])
      [[1, 2], [4, 5]]

      iex> Enum.chunk_every([1, 2, 3, 4], 3, 3, Stream.cycle([0]))
      [[1, 2, 3], [4, 0, 0]]

  """
  @doc since: "1.5.0"
  @spec chunk_every(t, pos_integer, pos_integer, t | :discard) :: [list]
  def chunk_every(enumerable, count, step, leftover \\ [])
      when is_integer(count) and count > 0 and is_integer(step) and step > 0 do
    R.chunk_every(&chunk_while/4, enumerable, count, step, leftover)
  end

  @doc """
  Chunks the `enumerable` with fine grained control when every chunk is emitted.

  `chunk_fun` receives the current element and the accumulator and must return:

    * `{:cont, chunk, acc}` to emit a chunk and continue with the accumulator
    * `{:cont, acc}` to not emit any chunk and continue with the accumulator
    * `{:halt, acc}` to halt chunking over the `enumerable`.

  `after_fun` is invoked with the final accumulator when iteration is
  finished (or `halt`ed) to handle any trailing elements that were returned
  as part of an accumulator, but were not emitted as a chunk by `chunk_fun`.
  It must return:

    * `{:cont, chunk, acc}` to emit a chunk. The chunk will be appended to the
      list of already emitted chunks.
    * `{:cont, acc}` to not emit a chunk

  The `acc` in `after_fun` is required in order to mirror the tuple format
  from `chunk_fun` but it will be discarded since the traversal is complete.

  Returns a list of emitted chunks.

  ## Examples

      iex> chunk_fun = fn element, acc ->
      ...>   if rem(element, 2) == 0 do
      ...>     {:cont, Enum.reverse([element | acc]), []}
      ...>   else
      ...>     {:cont, [element | acc]}
      ...>   end
      ...> end
      iex> after_fun = fn
      ...>   [] -> {:cont, []}
      ...>   acc -> {:cont, Enum.reverse(acc), []}
      ...> end
      iex> Enum.chunk_while(1..10, [], chunk_fun, after_fun)
      [[1, 2], [3, 4], [5, 6], [7, 8], [9, 10]]
      iex> Enum.chunk_while([1, 2, 3, 5, 7], [], chunk_fun, after_fun)
      [[1, 2], [3, 5, 7]]

  """
  @doc since: "1.5.0"
  @spec chunk_while(
          t,
          acc,
          (element, acc -> {:cont, chunk, acc} | {:cont, acc} | {:halt, acc}),
          (acc -> {:cont, chunk, acc} | {:cont, acc})
        ) :: Enumerable.t()
        when chunk: any
  def chunk_while(enumerable, acc, chunk_fun, after_fun) do
    {_, {res, acc}} =
      Enumerable.reduce(enumerable, {:cont, {[], acc}}, fn entry, {buffer, acc} ->
        case chunk_fun.(entry, acc) do
          {:cont, chunk, acc} -> {:cont, {[chunk | buffer], acc}}
          {:cont, acc} -> {:cont, {buffer, acc}}
          {:halt, acc} -> {:halt, {buffer, acc}}
        end
      end)

    case after_fun.(acc) do
      {:cont, _acc} -> :lists.reverse(res)
      {:cont, chunk, _acc} -> :lists.reverse([chunk | res])
    end
  end

  @doc """
  Splits enumerable on every element for which `fun` returns a new
  value.

  Returns a list of lists.

  ## Examples

      iex> Enum.chunk_by([1, 2, 2, 3, 4, 4, 6, 7, 7], &(rem(&1, 2) == 1))
      [[1], [2, 2], [3], [4, 4, 6], [7, 7]]

  """
  @spec chunk_by(t, (element -> any)) :: [list]
  def chunk_by(enumerable, fun) do
    R.chunk_by(&chunk_while/4, enumerable, fun)
  end

  @doc """
  Given an enumerable of enumerables, concatenates the `enumerables` into
  a single list.

  ## Examples

      iex> Enum.concat([1..3, 4..6, 7..9])
      [1, 2, 3, 4, 5, 6, 7, 8, 9]

      iex> Enum.concat([[1, [2], 3], [4], [5, 6]])
      [1, [2], 3, 4, 5, 6]

  """
  @spec concat(t) :: t
  def concat(enumerables)

  def concat(list) when is_list(list) do
    concat_list(list)
  end

  def concat(enums) do
    concat_enum(enums)
  end

  @doc """
  Concatenates the enumerable on the `right` with the enumerable on the
  `left`.

  This function produces the same result as the `++/2` operator
  for lists.

  ## Examples

      iex> Enum.concat(1..3, 4..6)
      [1, 2, 3, 4, 5, 6]

      iex> Enum.concat([1, 2, 3], [4, 5, 6])
      [1, 2, 3, 4, 5, 6]

  """
  @spec concat(t, t) :: t
  def concat(left, right) when is_list(left) and is_list(right) do
    left ++ right
  end

  def concat(left, right) do
    concat_enum([left, right])
  end

  @doc """
  Returns the size of the `enumerable`.

  ## Examples

      iex> Enum.count([1, 2, 3])
      3

  """
  @spec count(t) :: non_neg_integer
  def count(enumerable) when is_list(enumerable) do
    length(enumerable)
  end

  def count(enumerable) do
    case Enumerable.count(enumerable) do
      {:ok, value} when is_integer(value) ->
        value

      {:error, module} ->
        enumerable |> module.reduce({:cont, 0}, fn _, acc -> {:cont, acc + 1} end) |> elem(1)
    end
  end

  @doc """
  Returns the count of elements in the `enumerable` for which `fun` returns
  a truthy value.

  ## 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(enumerable, fun) do
    reduce(enumerable, 0, fn entry, acc ->
      if(fun.(entry), do: acc + 1, else: acc)
    end)
  end

  @doc """
  Counts the enumerable stopping at `limit`.

  This is useful for checking certain properties of the count of an enumerable
  without having to actually count the entire enumerable. For example, if you
  wanted to check that the count was exactly, at least, or more than a value.

  If the enumerable implements `c:Enumerable.count/1`, the enumerable is
  not traversed and we return the lower of the two numbers. To force
  enumeration, use `count_until/3` with `fn _ -> true end` as the second
  argument.

  ## Examples

      iex> Enum.count_until(1..20, 5)
      5
      iex> Enum.count_until(1..20, 50)
      20
      iex> Enum.count_until(1..10, 10) == 10 # At least 10
      true
      iex> Enum.count_until(1..11, 10 + 1) > 10 # More than 10
      true
      iex> Enum.count_until(1..5, 10) < 10 # Less than 10
      true
      iex> Enum.count_until(1..10, 10 + 1) == 10 # Exactly ten
      true

  """
  @doc since: "1.12.0"
  @spec count_until(t, pos_integer) :: non_neg_integer
  def count_until(enumerable, limit) when is_integer(limit) and limit > 0 do
    case enumerable do
      list when is_list(list) -> count_until_list(list, limit, 0)
      _ -> count_until_enum(enumerable, limit)
    end
  end

  def count_until(_enumerable, limit) when is_integer(limit) do
    raise ArgumentError, "expected limit to be greater than 0, got: #{limit}"
  end

  @doc """
  Counts the elements in the enumerable for which `fun` returns a truthy value, stopping at `limit`.

  See `count/2` and `count_until/2` for more information.

  ## Examples

      iex> Enum.count_until(1..20, fn x -> rem(x, 2) == 0 end, 7)
      7
      iex> Enum.count_until(1..20, fn x -> rem(x, 2) == 0 end, 11)
      10
  """
  @doc since: "1.12.0"
  @spec count_until(t, (element -> as_boolean(term)), pos_integer) :: non_neg_integer
  def count_until(enumerable, fun, limit) when is_integer(limit) and limit > 0 do
    case enumerable do
      list when is_list(list) -> count_until_list(list, fun, limit, 0)
      _ -> count_until_enum(enumerable, fun, limit)
    end
  end

  def count_until(_enumerable, _fun, limit) when is_integer(limit) do
    raise ArgumentError, "expected limit to be greater than 0, got: #{limit}"
  end

  @doc """
  Enumerates the `enumerable`, returning a list where all consecutive
  duplicate elements are collapsed to a single element.

  Elements are compared using `===/2`.

  If you want to remove all duplicate elements, regardless of order,
  see `uniq/1`.

  ## Examples

      iex> Enum.dedup([1, 2, 3, 3, 2, 1])
      [1, 2, 3, 2, 1]

      iex> Enum.dedup([1, 1, 2, 2.0, :three, :three])
      [1, 2, 2.0, :three]

  """
  @spec dedup(t) :: list
  def dedup(enumerable) when is_list(enumerable) do
    dedup_list(enumerable)
  end

  def dedup(enumerable) do
    reduce(enumerable, [], fn x, acc ->
      case acc do
        [^x | _] -> acc
        _ -> [x | acc]
      end
    end)
    |> :lists.reverse()
  end

  @doc """
  Enumerates the `enumerable`, returning a list where all consecutive
  duplicate elements are collapsed to a single element.

  The function `fun` maps every element to a term which is used to
  determine if two elements are duplicates.

  ## Examples

      iex> Enum.dedup_by([{1, :a}, {2, :b}, {2, :c}, {1, :a}], fn {x, _} -> x end)
      [{1, :a}, {2, :b}, {1, :a}]

      iex> Enum.dedup_by([5, 1, 2, 3, 2, 1], fn x -> x > 2 end)
      [5, 1, 3, 2]

  """
  @spec dedup_by(t, (element -> term)) :: list
  def dedup_by(enumerable, fun) do
    {list, _} = reduce(enumerable, {[], []}, R.dedup(fun))
    :lists.reverse(list)
  end

  @doc """
  Drops the `amount` of elements from the `enumerable`.

  If a negative `amount` is given, the `amount` of last values will be dropped.
  The `enumerable` will be enumerated once to retrieve the proper index and
  the remaining calculation is performed from the end.

  ## 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]

      iex> Enum.drop([1, 2, 3], -1)
      [1, 2]

  """
  @spec drop(t, integer) :: list
  def drop(enumerable, amount)
      when is_list(enumerable) and is_integer(amount) and amount >= 0 do
    drop_list(enumerable, amount)
  end

  def drop(enumerable, 0) do
    to_list(enumerable)
  end

  def drop(enumerable, amount) when is_integer(amount) and amount > 0 do
    {result, _} = reduce(enumerable, {[], amount}, R.drop())
    if is_list(result), do: :lists.reverse(result), else: []
  end

  def drop(enumerable, amount) when is_integer(amount) and amount < 0 do
    {count, fun} = slice_count_and_fun(enumerable, 1)
    amount = Kernel.min(amount + count, count)

    if amount > 0 do
      fun.(0, amount, 1)
    else
      []
    end
  end

  @doc """
  Returns a list of every `nth` element in the `enumerable` dropped,
  starting with the first element.

  The first element is always dropped, unless `nth` is 0.

  The second argument specifying every `nth` element must be a non-negative
  integer.

  ## Examples

      iex> Enum.drop_every(1..10, 2)
      [2, 4, 6, 8, 10]

      iex> Enum.drop_every(1..10, 0)
      [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

      iex> Enum.drop_every([1, 2, 3], 1)
      []

  """
  @spec drop_every(t, non_neg_integer) :: list
  def drop_every(enumerable, nth)

  def drop_every(_enumerable, 1), do: []
  def drop_every(enumerable, 0), do: to_list(enumerable)
  def drop_every([], nth) when is_integer(nth), do: []

  def drop_every(enumerable, nth) when is_integer(nth) and nth > 1 do
    {res, _} = reduce(enumerable, {[], :first}, R.drop_every(nth))
    :lists.reverse(res)
  end

  @doc """
  Drops elements at the beginning of the `enumerable` while `fun` returns a
  truthy value.

  ## Examples

      iex> Enum.drop_while([1, 2, 3, 2, 1], fn x -> x < 3 end)
      [3, 2, 1]

  """
  @spec drop_while(t, (element -> as_boolean(term))) :: list
  def drop_while(enumerable, fun) when is_list(enumerable) do
    drop_while_list(enumerable, fun)
  end

  def drop_while(enumerable, fun) do
    {res, _} = reduce(enumerable, {[], true}, R.drop_while(fun))
    :lists.reverse(res)
  end

  @doc """
  Invokes the given `fun` for each element in the `enumerable`.

  Returns `:ok`.

  ## Examples

      Enum.each(["some", "example"], fn x -> IO.puts(x) end)
      some
      example
      #=> :ok

  """
  @spec each(t, (element -> any)) :: :ok
  def each(enumerable, fun) when is_list(enumerable) do
    :lists.foreach(fun, enumerable)
  end

  def each(enumerable, fun) do
    reduce(enumerable, nil, fn entry, _ ->
      fun.(entry)
      nil
    end)

    :ok
  end

  @doc """
  Determines if the `enumerable` is empty.

  Returns `true` if `enumerable` is empty, otherwise `false`.

  ## Examples

      iex> Enum.empty?([])
      true

      iex> Enum.empty?([1, 2, 3])
      false

  """
  @spec empty?(t) :: boolean
  def empty?(enumerable) when is_list(enumerable) do
    enumerable == []
  end

  def empty?(enumerable) do
    case Enumerable.slice(enumerable) do
      {:ok, value, _} ->
        value == 0

      {:error, module} ->
        enumerable
        |> module.reduce({:cont, true}, fn _, _ -> {:halt, false} end)
        |> elem(1)
    end
  end

  @doc """
  Finds the element at the given `index` (zero-based).

  Returns `{:ok, element}` if found, otherwise `:error`.

  A negative `index` can be passed, which means the `enumerable` is
  enumerated once and the `index` is counted from the end (for example,
  `-1` fetches the last element).

  ## Examples

      iex> Enum.fetch([2, 4, 6], 0)
      {:ok, 2}

      iex> Enum.fetch([2, 4, 6], -3)
      {: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(enumerable, index) when is_integer(index) do
    case slice_forward(enumerable, index, 1, 1) do
      [value] -> {:ok, value}
      [] -> :error
    end
  end

  @doc """
  Finds the element at the given `index` (zero-based).

  Raises `OutOfBoundsError` if the given `index` is outside the range of
  the `enumerable`.

  ## 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 at position 4 when traversing enumerable [2, 4, 6]

  """
  @spec fetch!(t, index) :: element
  def fetch!(enumerable, index) when is_integer(index) do
    case slice_forward(enumerable, index, 1, 1) do
      [value] -> value
      [] -> raise Enum.OutOfBoundsError, index: index, enumerable: enumerable
    end
  end

  @doc """
  Filters the `enumerable`, i.e. returns only those elements
  for which `fun` returns a truthy value.

  See also `reject/2` which discards all elements where the
  function returns a truthy value.

  ## Examples

      iex> Enum.filter([1, 2, 3], fn x -> rem(x, 2) == 0 end)
      [2]
      iex> Enum.filter(["apple", "pear", "banana"], fn fruit -> String.contains?(fruit, "a") end)
      ["apple", "pear", "banana"]
      iex> Enum.filter([4, 21, 24, 904], fn seconds -> seconds > 1000 end)
      []

  Keep in mind that `filter` is not capable of filtering and
  transforming an element at the same time. If you would like
  to do so, consider using `flat_map/2`. For example, if you
  want to convert all strings that represent an integer and
  discard the invalid one in one pass:

      strings = ["1234", "abc", "12ab"]

      Enum.flat_map(strings, fn string ->
        case Integer.parse(string) do
          # transform to integer
          {int, _rest} -> [int]
          # skip the value
          :error -> []
        end
      end)

  """
  @spec filter(t, (element -> as_boolean(term))) :: list
  def filter(enumerable, fun) when is_list(enumerable) do
    filter_list(enumerable, fun)
  end

  def filter(enumerable, fun) do
    reduce(enumerable, [], R.filter(fun)) |> :lists.reverse()
  end

  @doc false
  @deprecated "Use Enum.filter/2 + Enum.map/2 or for comprehensions instead"
  def filter_map(enumerable, filter, mapper) when is_list(enumerable) do
    for element <- enumerable, filter.(element), do: mapper.(element)
  end

  def filter_map(enumerable, filter, mapper) do
    enumerable
    |> reduce([], R.filter_map(filter, mapper))
    |> :lists.reverse()
  end

  @doc """
  Returns the first element for which `fun` returns a truthy value.
  If no such element is found, returns `default`.

  ## Examples

      iex> Enum.find([2, 3, 4], fn x -> rem(x, 2) == 1 end)
      3

      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

  """
  @spec find(t, default, (element -> any)) :: element | default
  def find(enumerable, default \\ nil, fun)

  def find(enumerable, default, fun) when is_list(enumerable) do
    find_list(enumerable, default, fun)
  end

  def find(enumerable, default, fun) do
    Enumerable.reduce(enumerable, {:cont, default}, fn entry, default ->
      if fun.(entry), do: {:halt, entry}, else: {:cont, default}
    end)
    |> elem(1)
  end

  @doc """
  Similar to `find/3`, but returns the index (zero-based)
  of the element instead of the element itself.

  ## 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)) :: non_neg_integer | nil
  def find_index(enumerable, fun) when is_list(enumerable) do
    find_index_list(enumerable, 0, fun)
  end

  def find_index(enumerable, fun) do
    result =
      Enumerable.reduce(enumerable, {:cont, {:not_found, 0}}, fn entry, {_, index} ->
        if fun.(entry), do: {:halt, {:found, index}}, else: {:cont, {:not_found, index + 1}}
      end)

    case elem(result, 1) do
      {:found, index} -> index
      {:not_found, _} -> nil
    end
  end

  @doc """
  Similar to `find/3`, but returns the value of the function
  invocation instead of the element itself.

  The return value is considered to be found when the result is truthy
  (neither `nil` nor `false`).

  ## Examples

      iex> Enum.find_value([2, 3, 4], fn x ->
      ...>   if x > 2, do: x * x
      ...> end)
      9

      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

      iex> Enum.find_value([1, 2, 3], "no bools!", &is_boolean/1)
      "no bools!"

  """
  @spec find_value(t, default, (element -> found_value)) :: found_value | default
        when found_value: term
  def find_value(enumerable, default \\ nil, fun)

  def find_value(enumerable, default, fun) when is_list(enumerable) do
    find_value_list(enumerable, default, fun)
  end

  def find_value(enumerable, default, fun) do
    Enumerable.reduce(enumerable, {:cont, default}, fn entry, default ->
      fun_entry = fun.(entry)
      if fun_entry, do: {:halt, fun_entry}, else: {:cont, default}
    end)
    |> elem(1)
  end

  @doc """
  Maps the given `fun` over `enumerable` and flattens the result only one level deep.

  This function returns a new enumerable built by appending the result of invoking `fun`
  on each element of `enumerable` together; conceptually, this is similar to a
  combination of `map/2` and `concat/1`.

  ## Examples

      iex> Enum.flat_map([:a, :b, :c], fn x -> [x, x] end)
      [:a, :a, :b, :b, :c, :c]

      iex> Enum.flat_map([{1, 3}, {4, 6}], fn {x, y} -> x..y end)
      [1, 2, 3, 4, 5, 6]

      iex> Enum.flat_map([:a, :b, :c], fn x -> [[x]] end)
      [[:a], [:b], [:c]]

  This is frequently used to transform and filter in one pass, returning empty
  lists to exclude results:

      iex> Enum.flat_map([4, 0, 2, 0], fn x ->
      ...>   if x != 0, do: [1 / x], else: []
      ...> end)
      [0.25, 0.5]

  """
  @spec flat_map(t, (element -> t)) :: list
  def flat_map(enumerable, fun) when is_list(enumerable) do
    flat_map_list(enumerable, fun)
  end

  def flat_map(enumerable, fun) do
    reduce(enumerable, [], fn entry, acc ->
      case fun.(entry) do
        [] -> acc
        list when is_list(list) -> [list | acc]
        other -> [to_list(other) | acc]
      end
    end)
    |> flat_reverse([])
  end

  # the first clause is an optimization
  defp flat_reverse([[elem] | t], acc), do: flat_reverse(t, [elem | acc])
  defp flat_reverse([h | t], acc), do: flat_reverse(t, h ++ acc)
  defp flat_reverse([], acc), do: acc

  @doc """
  Maps and reduces an `enumerable`, flattening the results only one level deep.

  It expects an accumulator and a function that receives each enumerable
  element, and must return a tuple containing a new enumerable (often a list)
  with the new accumulator or a tuple with `:halt` as first element and
  the accumulator as second.

  Returns a 2-element tuple where the first element is the results flattened one level deep and
  the second element is the last accumulator.

  ## Examples

      iex> enumerable = 1..100
      iex> n = 3
      iex> Enum.flat_map_reduce(enumerable, 0, fn x, acc ->
      ...>   if acc < n, do: {[x], acc + 1}, else: {:halt, acc}
      ...> end)
      {[1, 2, 3], 3}

      iex> Enum.flat_map_reduce(1..5, 0, fn x, acc -> {[[x]], acc + x} end)
      {[[1], [2], [3], [4], [5]], 15}

  """
  @spec flat_map_reduce(t, acc, fun) :: {[any], acc}
        when fun: (element, acc -> {t, acc} | {:halt, acc})
  def flat_map_reduce(enumerable, acc, fun) do
    {_, {list, acc}} =
      Enumerable.reduce(enumerable, {:cont, {[], acc}}, fn entry, {list, acc} ->
        case fun.(entry, acc) do
          {:halt, acc} ->
            {:halt, {list, acc}}

          {[], acc} ->
            {:cont, {list, acc}}

          {[entry], acc} ->
            {:cont, {[entry | list], acc}}

          {entries, acc} ->
            {:cont, {reduce(entries, list, &[&1 | &2]), acc}}
        end
      end)

    {:lists.reverse(list), acc}
  end

  @doc """
  Returns a map with keys as unique elements of `enumerable` and values
  as the count of every element.

  ## Examples

      iex> Enum.frequencies(~w{ant buffalo ant ant buffalo dingo})
      %{"ant" => 3, "buffalo" => 2, "dingo" => 1}

  """
  @doc since: "1.10.0"
  @spec frequencies(t) :: map
  def frequencies(enumerable) do
    reduce(enumerable, %{}, fn key, acc ->
      case acc do
        %{^key => value} -> %{acc | key => value + 1}
        %{} -> Map.put(acc, key, 1)
      end
    end)
  end

  @doc """
  Returns a map with keys as unique elements given by `key_fun` and values
  as the count of every element.

  ## Examples

      iex> Enum.frequencies_by(~w{aa aA bb cc}, &String.downcase/1)
      %{"aa" => 2, "bb" => 1, "cc" => 1}

      iex> Enum.frequencies_by(~w{aaa aA bbb cc c}, &String.length/1)
      %{3 => 2, 2 => 2, 1 => 1}

  """
  @doc since: "1.10.0"
  @spec frequencies_by(t, (element -> any)) :: map
  def frequencies_by(enumerable, key_fun) when is_function(key_fun) do
    reduce(enumerable, %{}, fn entry, acc ->
      key = key_fun.(entry)

      case acc do
        %{^key => value} -> %{acc | key => value + 1}
        %{} -> Map.put(acc, key, 1)
      end
    end)
  end

  @doc """
  Splits the `enumerable` into groups based on `key_fun`.

  The result is a map where each key is given by `key_fun`
  and each value is a list of elements given by `value_fun`.
  The order of elements within each list is preserved from the `enumerable`.
  However, like all maps, the resulting map is unordered.

  ## Examples

      iex> Enum.group_by(~w{ant buffalo cat dingo}, &String.length/1)
      %{3 => ["ant", "cat"], 5 => ["dingo"], 7 => ["buffalo"]}

      iex> Enum.group_by(~w{ant buffalo cat dingo}, &String.length/1, &String.first/1)
      %{3 => ["a", "c"], 5 => ["d"], 7 => ["b"]}

  The key can be any Elixir value. For example, you may use a tuple
  to group by multiple keys:

      iex> collection = [
      ...>   %{id: 1, lang: "Elixir", seq: 1},
      ...>   %{id: 1, lang: "Java", seq: 1},
      ...>   %{id: 1, lang: "Ruby", seq: 2},
      ...>   %{id: 2, lang: "Python", seq: 1},
      ...>   %{id: 2, lang: "C#", seq: 2},
      ...>   %{id: 2, lang: "Haskell", seq: 2},
      ...> ]
      iex> Enum.group_by(collection, &{&1.id, &1.seq})
      %{
        {1, 1} => [%{id: 1, lang: "Elixir", seq: 1}, %{id: 1, lang: "Java", seq: 1}],
        {1, 2} => [%{id: 1, lang: "Ruby", seq: 2}],
        {2, 1} => [%{id: 2, lang: "Python", seq: 1}],
        {2, 2} => [%{id: 2, lang: "C#", seq: 2}, %{id: 2, lang: "Haskell", seq: 2}]
      }
      iex> Enum.group_by(collection, &{&1.id, &1.seq}, &{&1.id, &1.lang})
      %{
        {1, 1} => [{1, "Elixir"}, {1, "Java"}],
        {1, 2} => [{1, "Ruby"}],
        {2, 1} => [{2, "Python"}],
        {2, 2} => [{2, "C#"}, {2, "Haskell"}]
      }

  """
  @spec group_by(t, (element -> any), (element -> any)) :: map
  def group_by(enumerable, key_fun, value_fun \\ fn x -> x end)

  def group_by(enumerable, key_fun, value_fun) when is_function(key_fun) do
    reduce(reverse(enumerable), %{}, fn entry, acc ->
      key = key_fun.(entry)
      value = value_fun.(entry)

      case acc do
        %{^key => existing} -> %{acc | key => [value | existing]}
        %{} -> Map.put(acc, key, [value])
      end
    end)
  end

  def group_by(enumerable, dict, fun) do
    IO.warn(
      "Enum.group_by/3 with a map/dictionary as second element is deprecated. " <>
        "A map is used by default and it is no longer required to pass one to this function"
    )

    # Avoid warnings about Dict
    dict_module = String.to_atom("Dict")

    reduce(reverse(enumerable), dict, fn entry, categories ->
      dict_module.update(categories, fun.(entry), [entry], &[entry | &1])
    end)
  end

  @doc """
  Intersperses `separator` between each element of the enumeration.

  ## Examples

      iex> Enum.intersperse([1, 2, 3], 0)
      [1, 0, 2, 0, 3]

      iex> Enum.intersperse([1], 0)
      [1]

      iex> Enum.intersperse([], 0)
      []

  """
  @spec intersperse(t, element) :: list
  def intersperse(enumerable, separator) when is_list(enumerable) do
    case enumerable do
      [] -> []
      list -> intersperse_non_empty_list(list, separator)
    end
  end

  def intersperse(enumerable, separator) do
    list =
      enumerable
      |> reduce([], fn x, acc -> [x, separator | acc] end)
      |> :lists.reverse()

    # Head is a superfluous separator
    case list do
      [] -> []
      [_ | t] -> t
    end
  end

  @doc """
  Inserts the given `enumerable` into a `collectable`.

  Note that passing a non-empty list as the `collectable` is deprecated.
  If you're collecting into a non-empty keyword list, consider using
  `Keyword.merge(collectable, Enum.to_list(enumerable))`. If you're collecting
  into a non-empty list, consider something like `Enum.to_list(enumerable) ++ collectable`.

  ## Examples

      iex> Enum.into([1, 2], [])
      [1, 2]

      iex> Enum.into([a: 1, b: 2], %{})
      %{a: 1, b: 2}

      iex> Enum.into(%{a: 1}, %{b: 2})
      %{a: 1, b: 2}

      iex> Enum.into([a: 1, a: 2], %{})
      %{a: 2}

      iex> Enum.into([a: 2], %{a: 1, b: 3})
      %{a: 2, b: 3}

  """
  @spec into(Enumerable.t(), Collectable.t()) :: Collectable.t()
  def into(enumerable, collectable)

  def into(enumerable, []) do
    to_list(enumerable)
  end

  def into(enumerable, collectable) when is_struct(collectable, MapSet) do
    if MapSet.size(collectable) == 0 do
      MapSet.new(enumerable)
    else
      MapSet.new(enumerable) |> MapSet.union(collectable)
    end
  end

  def into(%_{} = enumerable, collectable) do
    into_protocol(enumerable, collectable)
  end

  def into(enumerable, %_{} = collectable) do
    into_protocol(enumerable, collectable)
  end

  def into(enumerable, %{} = collectable) do
    if map_size(collectable) == 0 do
      into_map(enumerable)
    else
      into_map(enumerable, collectable)
    end
  end

  def into(enumerable, collectable) do
    into_protocol(enumerable, collectable)
  end

  defp into_map(%{} = enumerable), do: enumerable
  defp into_map(enumerable) when is_list(enumerable), do: :maps.from_list(enumerable)
  defp into_map(enumerable), do: enumerable |> Enum.to_list() |> :maps.from_list()

  defp into_map(%{} = enumerable, collectable), do: Map.merge(collectable, enumerable)

  defp into_map(enumerable, collectable) when is_list(enumerable),
    do: Map.merge(collectable, :maps.from_list(enumerable))

  defp into_map(enumerable, collectable),
    do: reduce(enumerable, collectable, fn {key, val}, acc -> Map.put(acc, key, val) end)

  defp into_protocol(enumerable, collectable) do
    {initial, fun} = Collectable.into(collectable)

    try do
      reduce_into_protocol(enumerable, initial, fun)
    catch
      kind, reason ->
        fun.(initial, :halt)
        :erlang.raise(kind, reason, __STACKTRACE__)
    else
      acc -> fun.(acc, :done)
    end
  end

  defp reduce_into_protocol(enumerable, initial, fun) when is_list(enumerable) do
    :lists.foldl(fn x, acc -> fun.(acc, {:cont, x}) end, initial, enumerable)
  end

  defp reduce_into_protocol(enumerable, initial, fun) do
    enumerable
    |> Enumerable.reduce({:cont, initial}, fn x, acc ->
      {:cont, fun.(acc, {:cont, x})}
    end)
    |> elem(1)
  end

  @doc """
  Inserts the given `enumerable` into a `collectable` according to the
  transformation function.

  ## Examples

      iex> Enum.into([1, 2, 3], [], fn x -> x * 3 end)
      [3, 6, 9]

      iex> Enum.into(%{a: 1, b: 2}, %{c: 3}, fn {k, v} -> {k, v * 2} end)
      %{a: 2, b: 4, c: 3}

  """
  @spec into(Enumerable.t(), Collectable.t(), (term -> term)) :: Collectable.t()
  def into(enumerable, [], transform) do
    map(enumerable, transform)
  end

  def into(enumerable, collectable, transform) when is_struct(collectable, MapSet) do
    if MapSet.size(collectable) == 0 do
      MapSet.new(enumerable, transform)
    else
      MapSet.new(enumerable, transform) |> MapSet.union(collectable)
    end
  end

  def into(enumerable, %_{} = collectable, transform) do
    into_protocol(enumerable, collectable, transform)
  end

  def into(enumerable, %{} = collectable, transform) do
    if map_size(collectable) == 0 do
      enumerable |> map(transform) |> :maps.from_list()
    else
      reduce(enumerable, collectable, fn entry, acc ->
        {key, val} = transform.(entry)
        Map.put(acc, key, val)
      end)
    end
  end

  def into(enumerable, collectable, transform) do
    into_protocol(enumerable, collectable, transform)
  end

  defp into_protocol(enumerable, collectable, transform) do
    {initial, fun} = Collectable.into(collectable)

    try do
      reduce_into_protocol(enumerable, initial, transform, fun)
    catch
      kind, reason ->
        fun.(initial, :halt)
        :erlang.raise(kind, reason, __STACKTRACE__)
    else
      acc -> fun.(acc, :done)
    end
  end

  defp reduce_into_protocol(enumerable, initial, transform, fun) when is_list(enumerable) do
    :lists.foldl(fn x, acc -> fun.(acc, {:cont, transform.(x)}) end, initial, enumerable)
  end

  defp reduce_into_protocol(enumerable, initial, transform, fun) do
    enumerable
    |> Enumerable.reduce({:cont, initial}, fn x, acc ->
      {:cont, fun.(acc, {:cont, transform.(x)})}
    end)
    |> elem(1)
  end

  @doc """
  Joins the given `enumerable` into a string using `joiner` as a
  separator.

  If `joiner` is not passed at all, it defaults to an empty string.

  All elements in the `enumerable` must be convertible to a string
  or be a binary, otherwise an error is raised.

  ## Examples

      iex> Enum.join([1, 2, 3])
      "123"

      iex> Enum.join([1, 2, 3], " = ")
      "1 = 2 = 3"

      iex> Enum.join([["a", "b"], ["c", "d", "e", ["f", "g"]], "h", "i"], " ")
      "ab cdefg h i"

  """
  @spec join(t, binary()) :: binary()
  def join(enumerable, joiner \\ "")

  def join(enumerable, "") do
    enumerable
    |> map(&entry_to_string(&1))
    |> IO.iodata_to_binary()
  end

  def join(enumerable, joiner) when is_list(enumerable) and is_binary(joiner) do
    join_list(enumerable, joiner)
  end

  def join(enumerable, joiner) when is_binary(joiner) do
    reduced =
      reduce(enumerable, :first, fn
        entry, :first -> entry_to_string(entry)
        entry, acc -> [acc, joiner | entry_to_string(entry)]
      end)

    if reduced == :first do
      ""
    else
      IO.iodata_to_binary(reduced)
    end
  end

  @doc """
  Returns a list where each element is the result of invoking
  `fun` on each corresponding element of `enumerable`.

  For maps, the function expects 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(enumerable, fun)

  def map(enumerable, fun) when is_list(enumerable) do
    :lists.map(fun, enumerable)
  end

  def map(first..last//step, fun) do
    map_range(first, last, step, fun)
  end

  def map(enumerable, fun) do
    reduce(enumerable, [], R.map(fun)) |> :lists.reverse()
  end

  @doc """
  Returns a list of results of invoking `fun` on every `nth`
  element of `enumerable`, starting with the first element.

  The first element is always passed to the given function, unless `nth` is `0`.

  The second argument specifying every `nth` element must be a non-negative
  integer.

  If `nth` is `0`, then `enumerable` is directly converted to a list,
  without `fun` being ever applied.

  ## Examples

      iex> Enum.map_every(1..10, 2, fn x -> x + 1000 end)
      [1001, 2, 1003, 4, 1005, 6, 1007, 8, 1009, 10]

      iex> Enum.map_every(1..10, 3, fn x -> x + 1000 end)
      [1001, 2, 3, 1004, 5, 6, 1007, 8, 9, 1010]

      iex> Enum.map_every(1..5, 0, fn x -> x + 1000 end)
      [1, 2, 3, 4, 5]

      iex> Enum.map_every([1, 2, 3], 1, fn x -> x + 1000 end)
      [1001, 1002, 1003]

  """
  @doc since: "1.4.0"
  @spec map_every(t, non_neg_integer, (element -> any)) :: list
  def map_every(enumerable, nth, fun)

  def map_every(enumerable, 1, fun), do: map(enumerable, fun)
  def map_every(enumerable, 0, _fun), do: to_list(enumerable)
  def map_every([], nth, _fun) when is_integer(nth) and nth > 1, do: []

  def map_every(enumerable, nth, fun) when is_integer(nth) and nth > 1 do
    {res, _} = reduce(enumerable, {[], :first}, R.map_every(nth, fun))
    :lists.reverse(res)
  end

  @doc """
  Maps and intersperses the given enumerable in one pass.

  ## Examples

      iex> Enum.map_intersperse([1, 2, 3], :a, &(&1 * 2))
      [2, :a, 4, :a, 6]
  """
  @doc since: "1.10.0"
  @spec map_intersperse(t, element(), (element -> any())) :: list()
  def map_intersperse(enumerable, separator, mapper)

  def map_intersperse(enumerable, separator, mapper) when is_list(enumerable) do
    map_intersperse_list(enumerable, separator, mapper)
  end

  def map_intersperse(enumerable, separator, mapper) do
    reduced =
      reduce(enumerable, :first, fn
        entry, :first -> [mapper.(entry)]
        entry, acc -> [mapper.(entry), separator | acc]
      end)

    if reduced == :first do
      []
    else
      :lists.reverse(reduced)
    end
  end

  @doc """
  Maps and joins the given `enumerable` in one pass.

  If `joiner` is not passed at all, it defaults to an empty string.

  All elements returned from invoking the `mapper` must be convertible to
  a string, 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, String.t(), (element -> String.Chars.t())) :: String.t()
  def map_join(enumerable, joiner \\ "", mapper) when is_binary(joiner) do
    enumerable
    |> map_intersperse(joiner, &entry_to_string(mapper.(&1)))
    |> IO.iodata_to_binary()
  end

  @doc """
  Invokes the given function to each element in the `enumerable` to reduce
  it to a single element, while keeping an accumulator.

  Returns a tuple where the first element is the mapped enumerable and
  the second one is the final accumulator.

  The function, `fun`, receives two arguments: the first one is the
  element, and the second one is the accumulator. `fun` must return
  a tuple with two elements in the form of `{result, accumulator}`.

  For maps, 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, acc, (element, acc -> {element, acc})) :: {list, acc}
  def map_reduce(enumerable, acc, fun) when is_list(enumerable) do
    :lists.mapfoldl(fun, acc, enumerable)
  end

  def map_reduce(enumerable, acc, fun) do
    {list, acc} =
      reduce(enumerable, {[], acc}, fn entry, {list, acc} ->
        {new_entry, acc} = fun.(entry, acc)
        {[new_entry | list], acc}
      end)

    {:lists.reverse(list), acc}
  end

  @doc false
  def max(list = [_ | _]), do: :lists.max(list)

  @doc false
  def max(list = [_ | _], empty_fallback) when is_function(empty_fallback, 0) do
    :lists.max(list)
  end

  @doc false
  @spec max(t, (-> empty_result)) :: element | empty_result when empty_result: any
  def max(enumerable, empty_fallback) when is_function(empty_fallback, 0) do
    max(enumerable, &>=/2, empty_fallback)
  end

  @doc """
  Returns the maximal element in the `enumerable` according
  to Erlang's term ordering.

  By default, the comparison is done with the [`>=`](`>=/2`) sorter function.
  If multiple elements are considered maximal, the first one that
  was found is returned. If you want the last element considered
  maximal to be returned, the sorter function should not return true
  for equal elements.

  If the enumerable is empty, the provided `empty_fallback` is called.
  The default `empty_fallback` raises `Enum.EmptyError`.

  ## Examples

      iex> Enum.max([1, 2, 3])
      3

  The fact this function uses Erlang's term ordering means that the comparison
  is structural and not semantic. For example:

      iex> Enum.max([~D[2017-03-31], ~D[2017-04-01]])
      ~D[2017-03-31]

  In the example above, `max/2` returned March 31st instead of April 1st
  because the structural comparison compares the day before the year.
  For this reason, most structs provide a "compare" function, such as
  `Date.compare/2`, which receives two structs and returns `:lt` (less-than),
  `:eq` (equal to), and `:gt` (greater-than). If you pass a module as the
  sorting function, Elixir will automatically use the `compare/2` function
  of said module:

      iex> Enum.max([~D[2017-03-31], ~D[2017-04-01]], Date)
      ~D[2017-04-01]

  Finally, if you don't want to raise on empty enumerables, you can pass
  the empty fallback:

      iex> Enum.max([], &>=/2, fn -> 0 end)
      0

  """
  @spec max(t, (element, element -> boolean) | module()) ::
          element | empty_result
        when empty_result: any
  @spec max(t, (element, element -> boolean) | module(), (-> empty_result)) ::
          element | empty_result
        when empty_result: any
  def max(enumerable, sorter \\ &>=/2, empty_fallback \\ fn -> raise Enum.EmptyError end) do
    aggregate(enumerable, max_sort_fun(sorter), empty_fallback)
  end

  defp max_sort_fun(sorter) when is_function(sorter, 2), do: sorter
  defp max_sort_fun(module) when is_atom(module), do: &(module.compare(&1, &2) != :lt)

  @doc false
  @spec max_by(
          t,
          (element -> any),
          (-> empty_result) | (element, element -> boolean) | module()
        ) :: element | empty_result
        when empty_result: any
  def max_by(enumerable, fun, empty_fallback)
      when is_function(fun, 1) and is_function(empty_fallback, 0) do
    max_by(enumerable, fun, &>=/2, empty_fallback)
  end

  @doc """
  Returns the maximal element in the `enumerable` as calculated
  by the given `fun`.

  By default, the comparison is done with the [`>=`](`>=/2`) sorter function.
  If multiple elements are considered maximal, the first one that
  was found is returned. If you want the last element considered
  maximal to be returned, the sorter function should not return true
  for equal elements.

  Calls the provided `empty_fallback` function and returns its value if
  `enumerable` is empty. The default `empty_fallback` raises `Enum.EmptyError`.

  ## Examples

      iex> Enum.max_by(["a", "aa", "aaa"], fn x -> String.length(x) end)
      "aaa"

      iex> Enum.max_by(["a", "aa", "aaa", "b", "bbb"], &String.length/1)
      "aaa"

  The fact this function uses Erlang's term ordering means that the
  comparison is structural and not semantic. Therefore, if you want
  to compare structs, most structs provide a "compare" function, such as
  `Date.compare/2`, which receives two structs and returns `:lt` (less-than),
  `:eq` (equal to), and `:gt` (greater-than). If you pass a module as the
  sorting function, Elixir will automatically use the `compare/2` function
  of said module:

      iex> users = [
      ...>   %{name: "Ellis", birthday: ~D[1943-05-11]},
      ...>   %{name: "Lovelace", birthday: ~D[1815-12-10]},
      ...>   %{name: "Turing", birthday: ~D[1912-06-23]}
      ...> ]
      iex> Enum.max_by(users, &(&1.birthday), Date)
      %{name: "Ellis", birthday: ~D[1943-05-11]}

  Finally, if you don't want to raise on empty enumerables, you can pass
  the empty fallback:

      iex> Enum.max_by([], &String.length/1, fn -> nil end)
      nil

  """
  @spec max_by(
          t,
          (element -> any),
          (element, element -> boolean) | module(),
          (-> empty_result)
        ) :: element | empty_result
        when empty_result: any
  def max_by(enumerable, fun, sorter \\ &>=/2, empty_fallback \\ fn -> raise Enum.EmptyError end)
      when is_function(fun, 1) do
    aggregate_by(enumerable, fun, max_sort_fun(sorter), empty_fallback)
  end

  @doc """
  Checks if `element` exists within the `enumerable`.

  Membership is tested with the match (`===/2`) operator.

  ## Examples

      iex> Enum.member?(1..10, 5)
      true
      iex> Enum.member?(1..10, 5.0)
      false

      iex> Enum.member?([1.0, 2.0, 3.0], 2)
      false
      iex> Enum.member?([1.0, 2.0, 3.0], 2.000)
      true

      iex> Enum.member?([:a, :b, :c], :d)
      false


  When called outside guards, the [`in`](`in/2`) and [`not in`](`in/2`)
  operators work by using this function.
  """
  @spec member?(t, element) :: boolean
  def member?(enumerable, element) do
    __in__(element, enumerable)
  end

  @doc false
  def __in__(element, enumerable) when is_list(enumerable) do
    :lists.member(element, enumerable)
  end

  def __in__(element, enumerable) do
    case Enumerable.member?(enumerable, element) do
      {:ok, element} when is_boolean(element) ->
        element

      {:error, module} ->
        module.reduce(enumerable, {:cont, false}, fn
          v, _ when v === element -> {:halt, true}
          _, _ -> {:cont, false}
        end)
        |> elem(1)
    end
  end

  @doc false
  def min(list = [_ | _]), do: :lists.min(list)

  @doc false
  def min(list = [_ | _], empty_fallback) when is_function(empty_fallback, 0) do
    :lists.min(list)
  end

  @doc false
  @spec min(t, (-> empty_result)) :: element | empty_result when empty_result: any
  def min(enumerable, empty_fallback) when is_function(empty_fallback, 0) do
    min(enumerable, &<=/2, empty_fallback)
  end

  @doc """
  Returns the minimal element in the `enumerable` according
  to Erlang's term ordering.

  By default, the comparison is done with the [`<=`](`<=/2`) sorter function.
  If multiple elements are considered minimal, the first one that
  was found is returned. If you want the last element considered
  minimal to be returned, the sorter function should not return true
  for equal elements.

  If the enumerable is empty, the provided `empty_fallback` is called.
  The default `empty_fallback` raises `Enum.EmptyError`.

  ## Examples

      iex> Enum.min([1, 2, 3])
      1

  The fact this function uses Erlang's term ordering means that the comparison
  is structural and not semantic. For example:

      iex> Enum.min([~D[2017-03-31], ~D[2017-04-01]])
      ~D[2017-04-01]

  In the example above, `min/2` returned April 1st instead of March 31st
  because the structural comparison compares the day before the year.
  For this reason, most structs provide a "compare" function, such as
  `Date.compare/2`, which receives two structs and returns `:lt` (less-than),
  `:eq` (equal to), and `:gt` (greater-than). If you pass a module as the
  sorting function, Elixir will automatically use the `compare/2` function
  of said module:

      iex> Enum.min([~D[2017-03-31], ~D[2017-04-01]], Date)
      ~D[2017-03-31]

  Finally, if you don't want to raise on empty enumerables, you can pass
  the empty fallback:

      iex> Enum.min([], fn -> 0 end)
      0

  """
  @spec min(t, (element, element -> boolean) | module()) ::
          element | empty_result
        when empty_result: any
  @spec min(t, (element, element -> boolean) | module(), (-> empty_result)) ::
          element | empty_result
        when empty_result: any
  def min(enumerable, sorter \\ &<=/2, empty_fallback \\ fn -> raise Enum.EmptyError end) do
    aggregate(enumerable, min_sort_fun(sorter), empty_fallback)
  end

  defp min_sort_fun(sorter) when is_function(sorter, 2), do: sorter
  defp min_sort_fun(module) when is_atom(module), do: &(module.compare(&1, &2) != :gt)

  @doc false
  @spec min_by(
          t,
          (element -> any),
          (-> empty_result) | (element, element -> boolean) | module()
        ) :: element | empty_result
        when empty_result: any
  def min_by(enumerable, fun, empty_fallback)
      when is_function(fun, 1) and is_function(empty_fallback, 0) do
    min_by(enumerable, fun, &<=/2, empty_fallback)
  end

  @doc """
  Returns the minimal element in the `enumerable` as calculated
  by the given `fun`.

  By default, the comparison is done with the [`<=`](`<=/2`) sorter function.
  If multiple elements are considered minimal, the first one that
  was found is returned. If you want the last element considered
  minimal to be returned, the sorter function should not return true
  for equal elements.

  Calls the provided `empty_fallback` function and returns its value if
  `enumerable` is empty. The default `empty_fallback` raises `Enum.EmptyError`.

  ## Examples

      iex> Enum.min_by(["a", "aa", "aaa"], fn x -> String.length(x) end)
      "a"

      iex> Enum.min_by(["a", "aa", "aaa", "b", "bbb"], &String.length/1)
      "a"

  The fact this function uses Erlang's term ordering means that the
  comparison is structural and not semantic. Therefore, if you want
  to compare structs, most structs provide a "compare" function, such as
  `Date.compare/2`, which receives two structs and returns `:lt` (less-than),
  `:eq` (equal to), and `:gt` (greater-than). If you pass a module as the
  sorting function, Elixir will automatically use the `compare/2` function
  of said module:

      iex> users = [
      ...>   %{name: "Ellis", birthday: ~D[1943-05-11]},
      ...>   %{name: "Lovelace", birthday: ~D[1815-12-10]},
      ...>   %{name: "Turing", birthday: ~D[1912-06-23]}
      ...> ]
      iex> Enum.min_by(users, &(&1.birthday), Date)
      %{name: "Lovelace", birthday: ~D[1815-12-10]}

  Finally, if you don't want to raise on empty enumerables, you can pass
  the empty fallback:

      iex> Enum.min_by([], &String.length/1, fn -> nil end)
      nil

  """
  @spec min_by(
          t,
          (element -> any),
          (element, element -> boolean) | module(),
          (-> empty_result)
        ) :: element | empty_result
        when empty_result: any
  def min_by(enumerable, fun, sorter \\ &<=/2, empty_fallback \\ fn -> raise Enum.EmptyError end)
      when is_function(fun, 1) do
    aggregate_by(enumerable, fun, min_sort_fun(sorter), empty_fallback)
  end

  @doc """
  Returns a tuple with the minimal and the maximal elements in the
  enumerable.

  By default, the comparison is done with the [`<`](`</2`) sorter function,
  as the function must not return true for equal elements.

  ## Examples

      iex> Enum.min_max([2, 3, 1])
      {1, 3}

      iex> Enum.min_max(["foo", "bar", "baz"])
      {"bar", "foo"}

      iex> Enum.min_max([], fn -> {nil, nil} end)
      {nil, nil}

  The fact this function uses Erlang's term ordering means that the
  comparison is structural and not semantic. Therefore, if you want
  to compare structs, most structs provide a "compare" function, such as
  `Date.compare/2`, which receives two structs and returns `:lt` (less-than),
  `:eq` (equal to), and `:gt` (greater-than). If you pass a module as the
  sorting function, Elixir will automatically use the `compare/2` function
  of said module:

      iex> dates = [
      ...>   ~D[2019-01-01],
      ...>   ~D[2020-01-01],
      ...>   ~D[2018-01-01]
      ...> ]
      iex> Enum.min_max(dates, Date)
      {~D[2018-01-01], ~D[2020-01-01]}

  You can also pass a custom sorting function:

      iex> Enum.min_max([2, 3, 1], &>/2)
      {3, 1}

  Finally, if you don't want to raise on empty enumerables, you can pass
  the empty fallback:

      iex> Enum.min_max([], fn -> nil end)
      nil

  """
  @spec min_max(t, (element, element -> boolean) | module()) :: {element, element}
  @spec min_max(t, (-> empty_result)) :: {element, element} | empty_result when empty_result: any
  @spec min_max(t, (element, element -> boolean) | module(), (-> empty_result)) ::
          {element, element} | empty_result
        when empty_result: any

  def min_max(enumerable, sorter_or_empty_fallback \\ fn -> raise Enum.EmptyError end)

  def min_max(first..last//step = range, empty_fallback)
      when is_function(empty_fallback, 0) do
    case Range.size(range) do
      0 ->
        empty_fallback.()

      _ ->
        last = last - rem(last - first, step)
        {Kernel.min(first, last), Kernel.max(first, last)}
    end
  end

  def min_max(enumerable, empty_fallback)
      when is_function(empty_fallback, 0) do
    min_max(enumerable, &</2, empty_fallback)
  end

  def min_max(enumerable, sorter) when is_atom(sorter) do
    min_max(enumerable, min_max_sort_fun(sorter))
  end

  def min_max(enumerable, sorter) when is_function(sorter, 2) do
    min_max(enumerable, sorter, fn -> raise Enum.EmptyError end)
  end

  def min_max(enumerable, sorter, empty_fallback)
      when is_atom(sorter) and is_function(empty_fallback, 0) do
    min_max(enumerable, min_max_sort_fun(sorter), empty_fallback)
  end

  def min_max(enumerable, sorter, empty_fallback)
      when is_function(sorter, 2) and is_function(empty_fallback, 0) do
    first_fun = &[&1 | &1]

    reduce_fun = fn entry, [min | max] = acc ->
      cond do
        sorter.(entry, min) ->
          [entry | max]

        sorter.(max, entry) ->
          [min | entry]

        true ->
          acc
      end
    end

    case reduce_by(enumerable, first_fun, reduce_fun) do
      :empty -> empty_fallback.()
      [min | max] -> {min, max}
    end
  end

  @doc false
  @spec min_max_by(t, (element -> any), (-> empty_result)) :: {element, element} | empty_result
        when empty_result: any
  def min_max_by(enumerable, fun, empty_fallback)
      when is_function(fun, 1) and is_function(empty_fallback, 0) do
    min_max_by(enumerable, fun, &</2, empty_fallback)
  end

  @doc """
  Returns a tuple with the minimal and the maximal elements in the
  enumerable as calculated by the given function.

  By default, the comparison is done with the [`<`](`</2`) sorter function,
  as the function must not return `true` for equal elements.

  ## Examples

      iex> Enum.min_max_by(["aaa", "bb", "c"], fn x -> String.length(x) end)
      {"c", "aaa"}

      iex> Enum.min_max_by(["aaa", "a", "bb", "c", "ccc"], &String.length/1)
      {"a", "aaa"}

      iex> Enum.min_max_by([], &String.length/1, fn -> {nil, nil} end)
      {nil, nil}

  The fact this function uses Erlang's term ordering means that the
  comparison is structural and not semantic. Therefore, if you want
  to compare structs, most structs provide a "compare" function, such as
  `Date.compare/2`, which receives two structs and returns `:lt` (less-than),
  `:eq` (equal to), and `:gt` (greater-than). If you pass a module as the
  sorting function, Elixir will automatically use the `compare/2` function
  of said module:

      iex> users = [
      ...>   %{name: "Ellis", birthday: ~D[1943-05-11]},
      ...>   %{name: "Lovelace", birthday: ~D[1815-12-10]},
      ...>   %{name: "Turing", birthday: ~D[1912-06-23]}
      ...> ]
      iex> Enum.min_max_by(users, &(&1.birthday), Date)
      {
        %{name: "Lovelace", birthday: ~D[1815-12-10]},
        %{name: "Ellis", birthday: ~D[1943-05-11]}
      }

  Finally, if you don't want to raise on empty enumerables, you can pass
  the empty fallback:

      iex> Enum.min_max_by([], &String.length/1, fn -> nil end)
      nil

  """
  @spec min_max_by(t, (element -> any), (element, element -> boolean) | module()) ::
          {element, element} | empty_result
        when empty_result: any
  @spec min_max_by(
          t,
          (element -> any),
          (element, element -> boolean) | module(),
          (-> empty_result)
        ) :: {element, element} | empty_result
        when empty_result: any
  def min_max_by(
        enumerable,
        fun,
        sorter_or_empty_fallback \\ &</2,
        empty_fallback \\ fn -> raise Enum.EmptyError end
      )

  def min_max_by(enumerable, fun, sorter, empty_fallback)
      when is_function(fun, 1) and is_atom(sorter) and is_function(empty_fallback, 0) do
    min_max_by(enumerable, fun, min_max_sort_fun(sorter), empty_fallback)
  end

  def min_max_by(enumerable, fun, sorter, empty_fallback)
      when is_function(fun, 1) and is_function(sorter, 2) and is_function(empty_fallback, 0) do
    first_fun = fn entry ->
      fun_entry = fun.(entry)
      {entry, entry, fun_entry, fun_entry}
    end

    reduce_fun = fn entry, {prev_min, prev_max, fun_min, fun_max} = acc ->
      fun_entry = fun.(entry)

      cond do
        sorter.(fun_entry, fun_min) ->
          {entry, prev_max, fun_entry, fun_max}

        sorter.(fun_max, fun_entry) ->
          {prev_min, entry, fun_min, fun_entry}

        true ->
          acc
      end
    end

    case reduce_by(enumerable, first_fun, reduce_fun) do
      :empty -> empty_fallback.()
      {min, max, _, _} -> {min, max}
    end
  end

  defp min_max_sort_fun(module) when is_atom(module), do: &(module.compare(&1, &2) == :lt)

  @doc """
  Splits the `enumerable` in two lists according to the given function `fun`.

  Splits the given `enumerable` in two lists by calling `fun` with each element
  in the `enumerable` as its only argument. Returns a tuple with the first list
  containing all the elements in `enumerable` for which applying `fun` returned
  a truthy value, and a second list with all the elements for which applying
  `fun` returned a falsy value (`false` or `nil`).

  The elements in both the returned lists are in the same relative order as they
  were in the original enumerable (if such enumerable was ordered, like a
  list). See the examples below.

  ## Examples

      iex> Enum.split_with([5, 4, 3, 2, 1, 0], fn x -> rem(x, 2) == 0 end)
      {[4, 2, 0], [5, 3, 1]}

      iex> Enum.split_with([a: 1, b: -2, c: 1, d: -3], fn {_k, v} -> v < 0 end)
      {[b: -2, d: -3], [a: 1, c: 1]}

      iex> Enum.split_with([a: 1, b: -2, c: 1, d: -3], fn {_k, v} -> v > 50 end)
      {[], [a: 1, b: -2, c: 1, d: -3]}

      iex> Enum.split_with([], fn {_k, v} -> v > 50 end)
      {[], []}

  """
  @doc since: "1.4.0"
  @spec split_with(t, (element -> as_boolean(term))) :: {list, list}
  def split_with(enumerable, fun) do
    {acc1, acc2} =
      reduce(enumerable, {[], []}, 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 false
  @deprecated "Use Enum.split_with/2 instead"
  def partition(enumerable, fun) do
    split_with(enumerable, fun)
  end

  @doc """
  Returns a random element of an `enumerable`.

  Raises `Enum.EmptyError` if `enumerable` is empty.

  This function uses Erlang's [`:rand` module](`:rand`) to calculate
  the random value. Check its documentation for setting a
  different random algorithm or a different seed.

  If a range is passed into the function, this function will pick a
  random value between the range limits, without traversing the whole
  range (thus executing in constant time and constant memory).

  ## Examples

  The examples below use the `:exsss` pseudorandom algorithm since it's
  the default from Erlang/OTP 22:

      # Although not necessary, let's seed the random algorithm
      iex> :rand.seed(:exsss, {100, 101, 102})
      iex> Enum.random([1, 2, 3])
      2
      iex> Enum.random([1, 2, 3])
      1
      iex> Enum.random(1..1_000)
      309

  ## Implementation

  The random functions in this module implement reservoir sampling,
  which allows them to sample infinite collections. In particular,
  we implement Algorithm L, as described in by Kim-Hung Li in
  "Reservoir-Sampling Algorithms of Time Complexity O(n(1+log(N/n)))".
  """
  @spec random(t) :: element
  def random(enumerable)

  def random(enumerable) when is_list(enumerable) do
    case length(enumerable) do
      0 -> raise Enum.EmptyError
      length -> enumerable |> drop_list(random_count(length)) |> hd()
    end
  end

  def random(first.._//step = range) do
    case Range.size(range) do
      0 -> raise Enum.EmptyError
      size -> first + random_count(size) * step
    end
  end

  def random(enumerable) do
    result =
      case Enumerable.slice(enumerable) do
        {:ok, 0, _} ->
          []

        {:ok, count, fun} when is_function(fun, 1) ->
          slice_list(fun.(enumerable), random_count(count), 1, 1)

        {:ok, count, fun} when is_function(fun, 3) ->
          fun.(random_count(count), 1, 1)

        # TODO: Remove me on v2.0
        {:ok, count, fun} when is_function(fun, 2) ->
          IO.warn(
            "#{inspect(Enumerable.impl_for(enumerable))} must return a three arity function on slice/1"
          )

          fun.(random_count(count), 1)

        {:error, _} ->
          take_random(enumerable, 1)
      end

    case result do
      [] -> raise Enum.EmptyError
      [elem] -> elem
    end
  end

  defp random_count(count) do
    :rand.uniform(count) - 1
  end

  @doc """
  Invokes `fun` for each element in the `enumerable` with the
  accumulator.

  Raises `Enum.EmptyError` if `enumerable` is empty.

  The first element of the `enumerable` is used as the initial value
  of the accumulator. Then, the function is invoked with the next
  element and the accumulator. The result returned by the function
  is used as the accumulator for the next iteration, recursively.
  When the `enumerable` is done, the last accumulator is returned.

  Since the first element of the enumerable is used as the initial
  value of the accumulator, `fun` will only be executed `n - 1` times
  where `n` is the length of the enumerable. This function won't call
  the specified function for enumerables that are one-element long.

  If you wish to use another value for the accumulator, use
  `Enum.reduce/3`.

  ## Examples

      iex> Enum.reduce([1, 2, 3, 4], fn x, acc -> x * acc end)
      24

  """
  @spec reduce(t, (element, acc -> acc)) :: acc
  def reduce(enumerable, fun)

  def reduce([h | t], fun) do
    reduce(t, h, fun)
  end

  def reduce([], _fun) do
    raise Enum.EmptyError
  end

  def reduce(enumerable, fun) do
    Enumerable.reduce(enumerable, {:cont, :first}, fn
      x, {:acc, acc} -> {:cont, {:acc, fun.(x, acc)}}
      x, :first -> {:cont, {:acc, x}}
    end)
    |> elem(1)
    |> case do
      :first -> raise Enum.EmptyError
      {:acc, acc} -> acc
    end
  end

  @doc """
  Invokes `fun` for each element in the `enumerable` with the accumulator.

  The initial value of the accumulator is `acc`. The function is invoked for
  each element in the enumerable with the accumulator. The result returned
  by the function is used as the accumulator for the next iteration.
  The function returns the last accumulator.

  ## Examples

      iex> Enum.reduce([1, 2, 3], 0, fn x, acc -> x + acc end)
      6

      iex> Enum.reduce(%{a: 2, b: 3, c: 4}, 0, fn {_key, val}, acc -> acc + val end)
      9

  ## Reduce as a building block

  Reduce (sometimes called `fold`) is a basic building block in functional
  programming. Almost all of the functions in the `Enum` module can be
  implemented on top of reduce. Those functions often rely on other operations,
  such as `Enum.reverse/1`, which are optimized by the runtime.

  For example, we could implement `map/2` in terms of `reduce/3` as follows:

      def my_map(enumerable, fun) do
        enumerable
        |> Enum.reduce([], fn x, acc -> [fun.(x) | acc] end)
        |> Enum.reverse()
      end

  In the example above, `Enum.reduce/3` accumulates the result of each call
  to `fun` into a list in reverse order, which is correctly ordered at the
  end by calling `Enum.reverse/1`.

  Implementing functions like `map/2`, `filter/2` and others are a good
  exercise for understanding the power behind `Enum.reduce/3`. When an
  operation cannot be expressed by any of the functions in the `Enum`
  module, developers will most likely resort to `reduce/3`.
  """
  @spec reduce(t, acc, (element, acc -> acc)) :: acc
  def reduce(enumerable, acc, fun) when is_list(enumerable) do
    :lists.foldl(fun, acc, enumerable)
  end

  def reduce(first..last//step, acc, fun) do
    reduce_range(first, last, step, acc, fun)
  end

  def reduce(%_{} = enumerable, acc, fun) do
    reduce_enumerable(enumerable, acc, fun)
  end

  def reduce(%{} = enumerable, acc, fun) do
    :maps.fold(fn k, v, acc -> fun.({k, v}, acc) end, acc, enumerable)
  end

  def reduce(enumerable, acc, fun) do
    reduce_enumerable(enumerable, acc, fun)
  end

  @doc """
  Reduces `enumerable` until `fun` returns `{:halt, term}`.

  The return value for `fun` is expected to be

    * `{:cont, acc}` to continue the reduction with `acc` as the new
      accumulator or
    * `{:halt, acc}` to halt the reduction

  If `fun` returns `{:halt, acc}` the reduction is halted and the function
  returns `acc`. Otherwise, if the enumerable is exhausted, the function returns
  the accumulator of the last `{:cont, acc}`.

  ## Examples

      iex> Enum.reduce_while(1..100, 0, fn x, acc ->
      ...>   if x < 5 do
      ...>     {:cont, acc + x}
      ...>   else
      ...>     {:halt, acc}
      ...>   end
      ...> end)
      10
      iex> Enum.reduce_while(1..100, 0, fn x, acc ->
      ...>   if x > 0 do
      ...>     {:cont, acc + x}
      ...>   else
      ...>     {:halt, acc}
      ...>   end
      ...> end)
      5050

  """
  @spec reduce_while(t, any, (element, any -> {:cont, any} | {:halt, any})) :: any
  def reduce_while(enumerable, acc, fun) do
    Enumerable.reduce(enumerable, {:cont, acc}, fun) |> elem(1)
  end

  @doc """
  Returns a list of elements in `enumerable` excluding those for which the function `fun` returns
  a truthy value.

  See also `filter/2`.

  ## 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(enumerable, fun) when is_list(enumerable) do
    reject_list(enumerable, fun)
  end

  def reject(enumerable, fun) do
    reduce(enumerable, [], R.reject(fun)) |> :lists.reverse()
  end

  @doc """
  Returns a list of elements in `enumerable` in reverse order.

  ## Examples

      iex> Enum.reverse([1, 2, 3])
      [3, 2, 1]

  """
  @spec reverse(t) :: list
  def reverse(enumerable)

  def reverse([]), do: []
  def reverse([_] = list), do: list
  def reverse([element1, element2]), do: [element2, element1]
  def reverse([element1, element2 | rest]), do: :lists.reverse(rest, [element2, element1])
  def reverse(enumerable), do: reduce(enumerable, [], &[&1 | &2])

  @doc """
  Reverses the elements in `enumerable`, appends the `tail`, and returns
  it as a list.

  This is an optimization for
  `enumerable |> Enum.reverse() |> Enum.concat(tail)`.

  ## Examples

      iex> Enum.reverse([1, 2, 3], [4, 5, 6])
      [3, 2, 1, 4, 5, 6]

  """
  @spec reverse(t, t) :: list
  def reverse(enumerable, tail) when is_list(enumerable) do
    :lists.reverse(enumerable, to_list(tail))
  end

  def reverse(enumerable, tail) do
    reduce(enumerable, to_list(tail), fn entry, acc ->
      [entry | acc]
    end)
  end

  @doc """
  Reverses the `enumerable` in the range from initial `start_index`
  through `count` elements.

  If `count` is greater than the size of the rest of the `enumerable`,
  then this function will reverse the rest of the enumerable.

  ## Examples

      iex> Enum.reverse_slice([1, 2, 3, 4, 5, 6], 2, 4)
      [1, 2, 6, 5, 4, 3]

  """
  @spec reverse_slice(t, non_neg_integer, non_neg_integer) :: list
  def reverse_slice(enumerable, start_index, count)
      when is_integer(start_index) and start_index >= 0 and is_integer(count) and count >= 0 do
    list = reverse(enumerable)
    length = length(list)
    count = Kernel.min(count, length - start_index)

    if count > 0 do
      reverse_slice(list, length, start_index + count, count, [])
    else
      :lists.reverse(list)
    end
  end

  @doc """
  Slides a single or multiple elements given by `range_or_single_index` from `enumerable`
  to `insertion_index`.

  The semantics of the range to be moved match the semantics of `Enum.slice/2`.
  Specifically, that means:

   * Indices are normalized, meaning that negative indexes will be counted from the end
      (for example, -1 means the last element of the enumerable). This will result in *two*
      traversals of your enumerable on types like lists that don't provide a constant-time count.

    * If the normalized index range's `last` is out of bounds, the range is truncated to the last element.

    * If the normalized index range's `first` is out of bounds, the selected range for sliding
      will be empty, so you'll get back your input list.

    * Decreasing ranges (such as `5..0//1`) also select an empty range to be moved,
      so you'll get back your input list.

    * Ranges with any step but 1 will raise an error.

  ## Examples

      # Slide a single element
      iex> Enum.slide([:a, :b, :c, :d, :e, :f, :g], 5, 1)
      [:a, :f, :b, :c, :d, :e, :g]

      # Slide a range of elements towards the head of the list
      iex> Enum.slide([:a, :b, :c, :d, :e, :f, :g], 3..5, 1)
      [:a, :d, :e, :f, :b, :c, :g]

      # Slide a range of elements towards the tail of the list
      iex> Enum.slide([:a, :b, :c, :d, :e, :f, :g], 1..3, 5)
      [:a, :e, :f, :b, :c, :d, :g]

      # Slide with negative indices (counting from the end)
      iex> Enum.slide([:a, :b, :c, :d, :e, :f, :g], 3..-1//1, 2)
      [:a, :b, :d, :e, :f, :g, :c]
      iex> Enum.slide([:a, :b, :c, :d, :e, :f, :g], -4..-2, 1)
      [:a, :d, :e, :f, :b, :c, :g]

      # Insert at negative indices (counting from the end)
      iex> Enum.slide([:a, :b, :c, :d, :e, :f, :g], 3, -1)
      [:a, :b, :c, :e, :f, :g, :d]

  """
  @doc since: "1.13.0"
  @spec slide(t, Range.t() | index, index) :: list
  def slide(enumerable, range_or_single_index, insertion_index)

  def slide(enumerable, single_index, insertion_index) when is_integer(single_index) do
    slide(enumerable, single_index..single_index, insertion_index)
  end

  # This matches the behavior of Enum.slice/2
  def slide(_, _.._//step = index_range, _insertion_index) when step != 1 do
    raise ArgumentError,
          "Enum.slide/3 does not accept ranges with custom steps, got: #{inspect(index_range)}"
  end

  # Normalize negative input ranges like Enum.slice/2
  def slide(enumerable, first..last//_, insertion_index)
      when first < 0 or last < 0 or insertion_index < 0 do
    count = Enum.count(enumerable)
    normalized_first = if first >= 0, do: first, else: Kernel.max(first + count, 0)
    normalized_last = if last >= 0, do: last, else: last + count

    normalized_insertion_index =
      if insertion_index >= 0, do: insertion_index, else: insertion_index + count

    if normalized_first < count and normalized_first != normalized_insertion_index do
      normalized_range = normalized_first..normalized_last//1
      slide(enumerable, normalized_range, normalized_insertion_index)
    else
      Enum.to_list(enumerable)
    end
  end

  def slide(enumerable, insertion_index.._//_, insertion_index) do
    Enum.to_list(enumerable)
  end

  def slide(_, first..last//_, insertion_index)
      when insertion_index > first and insertion_index <= last do
    raise ArgumentError,
          "insertion index for slide must be outside the range being moved " <>
            "(tried to insert #{first}..#{last} at #{insertion_index})"
  end

  def slide(enumerable, first..last//_, _insertion_index) when first > last do
    Enum.to_list(enumerable)
  end

  # Guarantees at this point: step size == 1 and first <= last and (insertion_index < first or insertion_index > last)
  def slide(enumerable, first..last//_, insertion_index) do
    impl = if is_list(enumerable), do: &slide_list_start/4, else: &slide_any/4

    cond do
      insertion_index <= first -> impl.(enumerable, insertion_index, first, last)
      insertion_index > last -> impl.(enumerable, first, last + 1, insertion_index)
    end
  end

  # Takes the range from middle..last and moves it to be in front of index start
  defp slide_any(enumerable, start, middle, last) do
    # We're going to deal with 4 "chunks" of the enumerable:
    # 0. "Head," before the start index
    # 1. "Slide back," between start (inclusive) and middle (exclusive)
    # 2. "Slide front," between middle (inclusive) and last (inclusive)
    # 3. "Tail," after last
    #
    # But, we're going to accumulate these into only two lists: pre and post.
    # We'll reverse-accumulate the head into our pre list, then "slide back" into post,
    # then "slide front" into pre, then "tail" into post.
    #
    # Then at the end, we're going to reassemble and reverse them, and end up with the
    # chunks in the correct order.
    {_size, pre, post} =
      reduce(enumerable, {0, [], []}, fn item, {index, pre, post} ->
        {pre, post} =
          cond do
            index < start -> {[item | pre], post}
            index >= start and index < middle -> {pre, [item | post]}
            index >= middle and index <= last -> {[item | pre], post}
            true -> {pre, [item | post]}
          end

        {index + 1, pre, post}
      end)

    :lists.reverse(pre, :lists.reverse(post))
  end

  # Like slide_any/4 above, this optimized implementation of slide for lists depends
  # on the indices being sorted such that we're moving middle..last to be in front of start.
  defp slide_list_start([h | t], start, middle, last)
       when start > 0 and start <= middle and middle <= last do
    [h | slide_list_start(t, start - 1, middle - 1, last - 1)]
  end

  defp slide_list_start(list, 0, middle, last), do: slide_list_middle(list, middle, last, [])
  defp slide_list_start([], _start, _middle, _last), do: []

  defp slide_list_middle([h | t], middle, last, acc) when middle > 0 do
    slide_list_middle(t, middle - 1, last - 1, [h | acc])
  end

  defp slide_list_middle(list, 0, last, start_to_middle) do
    {slid_range, tail} = slide_list_last(list, last + 1, [])
    slid_range ++ :lists.reverse(start_to_middle, tail)
  end

  # You asked for a middle index off the end of the list... you get what we've got
  defp slide_list_middle([], _, _, acc) do
    :lists.reverse(acc)
  end

  defp slide_list_last([h | t], last, acc) when last > 0 do
    slide_list_last(t, last - 1, [h | acc])
  end

  defp slide_list_last(rest, 0, acc) do
    {:lists.reverse(acc), rest}
  end

  defp slide_list_last([], _, acc) do
    {:lists.reverse(acc), []}
  end

  @doc """
  Passes each element from `enumerable` to the `fun` as the first argument,
  stores the `fun` result in a list and passes the result as the second argument
  for the next computation.

  The `fun` isn't applied for the first element of the `enumerable`,
  the element is taken as it is.

  ## Examples

      iex> Enum.scan(["a", "b", "c", "d", "e"], fn element, acc -> element <> String.first(acc) end)
      ["a", "ba", "cb", "dc", "ed"]

      iex> Enum.scan(1..5, fn element, acc -> element + acc end)
      [1, 3, 6, 10, 15]

  """
  @spec scan(t, (element, any -> any)) :: list
  def scan(enumerable, fun)

  def scan([], _fun), do: []

  def scan([elem | rest], fun) do
    scanned = scan_list(rest, elem, fun)
    [elem | scanned]
  end

  def scan(enumerable, fun) do
    {res, _} = reduce(enumerable, {[], :first}, R.scan2(fun))
    :lists.reverse(res)
  end

  @doc """
  Passes each element from `enumerable` to the `fun` as the first argument,
  stores the `fun` result in a list and passes the result as the second argument
  for the next computation.

  Passes the given `acc` as the second argument for the `fun` with the first element.

  ## Examples

      iex> Enum.scan(["a", "b", "c", "d", "e"], "_", fn element, acc -> element <> String.first(acc) end)
      ["a_", "ba", "cb", "dc", "ed"]

      iex> Enum.scan(1..5, 0, fn element, acc -> element + acc end)
      [1, 3, 6, 10, 15]

  """
  @spec scan(t, any, (element, any -> any)) :: list
  def scan(enumerable, acc, fun) when is_list(enumerable) do
    scan_list(enumerable, acc, fun)
  end

  def scan(enumerable, acc, fun) do
    {res, _} = reduce(enumerable, {[], acc}, R.scan3(fun))
    :lists.reverse(res)
  end

  @doc """
  Returns a list with the elements of `enumerable` shuffled.

  This function uses Erlang's [`:rand` module](`:rand`) to calculate
  the random value. Check its documentation for setting a
  different random algorithm or a different seed.

  ## Examples

  The examples below use the `:exsss` pseudorandom algorithm since it's
  the default from Erlang/OTP 22:

      # Although not necessary, let's seed the random algorithm
      iex> :rand.seed(:exsss, {11, 22, 33})
      iex> Enum.shuffle([1, 2, 3])
      [2, 1, 3]
      iex> Enum.shuffle([1, 2, 3])
      [2, 3, 1]

  """
  @spec shuffle(t) :: list
  def shuffle(enumerable) do
    randomized =
      reduce(enumerable, [], fn x, acc ->
        [{:rand.uniform(), x} | acc]
      end)

    shuffle_unwrap(:lists.keysort(1, randomized))
  end

  defp shuffle_unwrap([{_, h} | rest]), do: [h | shuffle_unwrap(rest)]
  defp shuffle_unwrap([]), do: []

  @doc """
  Returns a subset list of the given `enumerable` by `index_range`.

  `index_range` must be a `Range`. Given an `enumerable`, it drops
  elements before `index_range.first` (zero-base), then it takes elements
  until element `index_range.last` (inclusively).

  Indexes are normalized, meaning that negative indexes will be counted
  from the end (for example, `-1` means the last element of the `enumerable`).

  If `index_range.last` is out of bounds, then it is assigned as the index
  of the last element.

  If the normalized `index_range.first` is out of bounds of the given
  `enumerable`, or this one is greater than the normalized `index_range.last`,
  then `[]` is returned.

  If a step `n` (other than `1`) is used in `index_range`, then it takes
  every `n`th element from `index_range.first` to `index_range.last`
  (according to the same rules described above).

  ## Examples

      iex> Enum.slice([1, 2, 3, 4, 5], 1..3)
      [2, 3, 4]

      iex> Enum.slice([1, 2, 3, 4, 5], 3..10)
      [4, 5]

      # Last three elements (negative indexes)
      iex> Enum.slice([1, 2, 3, 4, 5], -3..-1)
      [3, 4, 5]

  For ranges where `start > stop`, you need to explicit
  mark them as increasing:

      iex> Enum.slice([1, 2, 3, 4, 5], 1..-2//1)
      [2, 3, 4]

  The step can be any positive number. For example, to
  get every 2 elements of the collection:

      iex> Enum.slice([1, 2, 3, 4, 5], 0..-1//2)
      [1, 3, 5]

  To get every third element of the first ten elements:

      iex> integers = Enum.to_list(1..20)
      iex> Enum.slice(integers, 0..9//3)
      [1, 4, 7, 10]

  If the first position is after the end of the enumerable
  or after the last position of the range, it returns an
  empty list:

      iex> Enum.slice([1, 2, 3, 4, 5], 6..10)
      []

      # first is greater than last
      iex> Enum.slice([1, 2, 3, 4, 5], 6..5//1)
      []

  """
  @doc since: "1.6.0"
  @spec slice(t, Range.t()) :: list
  def slice(enumerable, first..last//step = index_range) do
    # TODO: Support negative steps as a reverse on Elixir v2.0.
    cond do
      step > 0 ->
        slice_range(enumerable, first, last, step)

      step == -1 and first > last ->
        IO.warn(
          "negative steps are not supported in Enum.slice/2, pass #{first}..#{last}//1 instead"
        )

        slice_range(enumerable, first, last, 1)

      true ->
        raise ArgumentError,
              "Enum.slice/2 does not accept ranges with negative steps, got: #{inspect(index_range)}"
    end
  end

  # TODO: Remove me on v2.0
  def slice(enumerable, %{__struct__: Range, first: first, last: last} = index_range) do
    step = if first <= last, do: 1, else: -1
    slice(enumerable, Map.put(index_range, :step, step))
  end

  defp slice_range(enumerable, first, -1, step) when first >= 0 do
    if step == 1 do
      drop(enumerable, first)
    else
      enumerable |> drop(first) |> take_every_list(step - 1)
    end
  end

  defp slice_range(enumerable, first, last, step)
       when last >= first and last >= 0 and first >= 0 do
    slice_forward(enumerable, first, last - first + 1, step)
  end

  defp slice_range(enumerable, first, last, step) do
    {count, fun} = slice_count_and_fun(enumerable, step)
    first = if first >= 0, do: first, else: Kernel.max(first + count, 0)
    last = if last >= 0, do: last, else: last + count
    amount = last - first + 1

    if first < count and amount > 0 do
      amount = Kernel.min(amount, count - first)
      amount = amount_with_step(amount, step)
      fun.(first, amount, step)
    else
      []
    end
  end

  defp amount_with_step(amount, 1), do: amount
  defp amount_with_step(amount, step), do: div(amount - 1, step) + 1

  @doc """
  Returns a subset list of the given `enumerable`, from `start_index` (zero-based)
  with `amount` number of elements if available.

  Given an `enumerable`, it drops elements right before element `start_index`;
  then, it takes `amount` of elements, returning as many elements as possible if
  there are not enough elements.

  A negative `start_index` can be passed, which means the `enumerable` is
  enumerated once and the index is counted from the end (for example,
  `-1` starts slicing from the last element).

  It returns `[]` if `amount` is `0` or if `start_index` is out of bounds.

  ## Examples

      iex> Enum.slice(1..100, 5, 10)
      [6, 7, 8, 9, 10, 11, 12, 13, 14, 15]

      # amount to take is greater than the number of elements
      iex> Enum.slice(1..10, 5, 100)
      [6, 7, 8, 9, 10]

      iex> Enum.slice(1..10, 5, 0)
      []

      # using a negative start index
      iex> Enum.slice(1..10, -6, 3)
      [5, 6, 7]
      iex> Enum.slice(1..10, -11, 5)
      [1, 2, 3, 4, 5]

      # out of bound start index
      iex> Enum.slice(1..10, 10, 5)
      []

  """
  @spec slice(t, index, non_neg_integer) :: list
  def slice(_enumerable, start_index, 0) when is_integer(start_index), do: []

  def slice(enumerable, start_index, amount)
      when is_integer(start_index) and start_index < 0 and is_integer(amount) and amount >= 0 do
    {count, fun} = slice_count_and_fun(enumerable, 1)
    start_index = Kernel.max(count + start_index, 0)
    amount = Kernel.min(amount, count - start_index)

    if amount > 0 do
      fun.(start_index, amount, 1)
    else
      []
    end
  end

  def slice(enumerable, start_index, amount)
      when is_integer(start_index) and is_integer(amount) and amount >= 0 do
    slice_forward(enumerable, start_index, amount, 1)
  end

  @doc """
  Sorts the `enumerable` according to Erlang's term ordering.

  This function uses the merge sort algorithm. Do not use this
  function to sort structs, see `sort/2` for more information.

  ## Examples

      iex> Enum.sort([3, 2, 1])
      [1, 2, 3]

  """
  @spec sort(t) :: list
  def sort(enumerable) when is_list(enumerable) do
    :lists.sort(enumerable)
  end

  def sort(enumerable) do
    sort(enumerable, &(&1 <= &2))
  end

  @doc """
  Sorts the `enumerable` by the given function.

  This function uses the merge sort algorithm. The given function should compare
  two arguments, and return `true` if the first argument precedes or is in the
  same place as the second one.

  ## Examples

      iex> Enum.sort([1, 2, 3], &(&1 >= &2))
      [3, 2, 1]

  The sorting algorithm will be stable as long as the given function
  returns `true` for values considered equal:

      iex> Enum.sort(["some", "kind", "of", "monster"], &(byte_size(&1) <= byte_size(&2)))
      ["of", "some", "kind", "monster"]

  If the function does not return `true` for equal values, the sorting
  is not stable and the order of equal terms may be shuffled.
  For example:

      iex> Enum.sort(["some", "kind", "of", "monster"], &(byte_size(&1) < byte_size(&2)))
      ["of", "kind", "some", "monster"]

  ## Ascending and descending (since v1.10.0)

  `sort/2` allows a developer to pass `:asc` or `:desc` as the sorter, which is a convenience for
  [`&<=/2`](`<=/2`) and [`&>=/2`](`>=/2`) respectively.

      iex> Enum.sort([2, 3, 1], :asc)
      [1, 2, 3]
      iex> Enum.sort([2, 3, 1], :desc)
      [3, 2, 1]

  ## Sorting structs

  Do not use `</2`, `<=/2`, `>/2`, `>=/2` and friends when sorting structs.
  That's because the built-in operators above perform structural comparison
  and not a semantic one. Imagine we sort the following list of dates:

      iex> dates = [~D[2019-01-01], ~D[2020-03-02], ~D[2019-06-06]]
      iex> Enum.sort(dates)
      [~D[2019-01-01], ~D[2020-03-02], ~D[2019-06-06]]

  Note that the returned result is incorrect, because `sort/1` by default uses
  `<=/2`, which will compare their structure. When comparing structures, the
  fields are compared in alphabetical order, which means the dates above will
  be compared by `day`, `month` and then `year`, which is the opposite of what
  we want.

  For this reason, most structs provide a "compare" function, such as
  `Date.compare/2`, which receives two structs and returns `:lt` (less-than),
  `:eq` (equal to), and `:gt` (greater-than). If you pass a module as the
  sorting function, Elixir will automatically use the `compare/2` function
  of said module:

      iex> dates = [~D[2019-01-01], ~D[2020-03-02], ~D[2019-06-06]]
      iex> Enum.sort(dates, Date)
      [~D[2019-01-01], ~D[2019-06-06], ~D[2020-03-02]]

  To retrieve all dates in descending order, you can wrap the module in
  a tuple with `:asc` or `:desc` as first element:

      iex> dates = [~D[2019-01-01], ~D[2020-03-02], ~D[2019-06-06]]
      iex> Enum.sort(dates, {:asc, Date})
      [~D[2019-01-01], ~D[2019-06-06], ~D[2020-03-02]]
      iex> dates = [~D[2019-01-01], ~D[2020-03-02], ~D[2019-06-06]]
      iex> Enum.sort(dates, {:desc, Date})
      [~D[2020-03-02], ~D[2019-06-06], ~D[2019-01-01]]

  """
  @spec sort(
          t,
          (element, element -> boolean) | :asc | :desc | module() | {:asc | :desc, module()}
        ) :: list
  def sort(enumerable, sorter) when is_list(enumerable) do
    case sorter do
      :asc -> :lists.sort(enumerable)
      :desc -> :lists.sort(enumerable) |> :lists.reverse()
      _ -> :lists.sort(to_sort_fun(sorter), enumerable)
    end
  end

  def sort(enumerable, sorter) do
    fun = to_sort_fun(sorter)

    reduce(enumerable, [], &sort_reducer(&1, &2, fun))
    |> sort_terminator(fun)
  end

  defp to_sort_fun(sorter) when is_function(sorter, 2), do: sorter
  defp to_sort_fun(:asc), do: &<=/2
  defp to_sort_fun(:desc), do: &>=/2
  defp to_sort_fun(module) when is_atom(module), do: &(module.compare(&1, &2) != :gt)
  defp to_sort_fun({:asc, module}) when is_atom(module), do: &(module.compare(&1, &2) != :gt)
  defp to_sort_fun({:desc, module}) when is_atom(module), do: &(module.compare(&1, &2) != :lt)

  @doc """
  Sorts the mapped results of the `enumerable` according to the provided `sorter`
  function.

  This function maps each element of the `enumerable` using the
  provided `mapper` function. The enumerable is then sorted by
  the mapped elements using the `sorter`, which defaults to `:asc`
  and sorts the elements ascendingly.

  `sort_by/3` differs from `sort/2` in that it only calculates the
  comparison value for each element in the enumerable once instead of
  once for each element in each comparison. If the same function is
  being called on both elements, it's more efficient to use `sort_by/3`.

  ## Ascending and descending (since v1.10.0)

  `sort_by/3` allows a developer to pass `:asc` or `:desc` as the sorter,
  which is a convenience for [`&<=/2`](`<=/2`) and [`&>=/2`](`>=/2`) respectively:
      iex> Enum.sort_by([2, 3, 1], &(&1), :asc)
      [1, 2, 3]

      iex> Enum.sort_by([2, 3, 1], &(&1), :desc)
      [3, 2, 1]

  ## Examples

  Using the default `sorter` of `:asc` :

      iex> Enum.sort_by(["some", "kind", "of", "monster"], &byte_size/1)
      ["of", "some", "kind", "monster"]

  Sorting by multiple properties - first by size, then by first letter
  (this takes advantage of the fact that tuples are compared element-by-element):

      iex> Enum.sort_by(["some", "kind", "of", "monster"], &{byte_size(&1), String.first(&1)})
      ["of", "kind", "some", "monster"]

  Similar to `sort/2`, you can pass a custom sorter:

      iex> Enum.sort_by(["some", "kind", "of", "monster"], &byte_size/1, :desc)
      ["monster", "some", "kind", "of"]

  As in `sort/2`, avoid using the default sorting function to sort
  structs, as by default it performs structural comparison instead of
  a semantic one. In such cases, you shall pass a sorting function as
  third element or any module that implements a `compare/2` function.
  For example, to sort users by their birthday in both ascending and
  descending order respectively:

      iex> users = [
      ...>   %{name: "Ellis", birthday: ~D[1943-05-11]},
      ...>   %{name: "Lovelace", birthday: ~D[1815-12-10]},
      ...>   %{name: "Turing", birthday: ~D[1912-06-23]}
      ...> ]
      iex> Enum.sort_by(users, &(&1.birthday), Date)
      [
        %{name: "Lovelace", birthday: ~D[1815-12-10]},
        %{name: "Turing", birthday: ~D[1912-06-23]},
        %{name: "Ellis", birthday: ~D[1943-05-11]}
      ]
      iex> Enum.sort_by(users, &(&1.birthday), {:desc, Date})
      [
        %{name: "Ellis", birthday: ~D[1943-05-11]},
        %{name: "Turing", birthday: ~D[1912-06-23]},
        %{name: "Lovelace", birthday: ~D[1815-12-10]}
      ]

  ## Performance characteristics

  As detailed in the initial section, `sort_by/3` calculates the comparison
  value for each element in the enumerable once instead of once for each
  element in each comparison. This implies `sort_by/3` must do an initial
  pass on the data to compute those values.

  However, if those values are cheap to compute, for example, you have
  already extracted the field you want to sort by into a tuple, then those
  extra passes become overhead. In such cases, consider using `List.keysort/3`
  instead.

  Let's see an example. Imagine you have a list of products and you have a
  list of IDs. You want to keep all products that are in the given IDs and
  return their names sorted by their price. You could write it like this:

      for(
        product <- products,
        product.id in ids,
        do: product
      )
      |> Enum.sort_by(& &1.price)
      |> Enum.map(& &1.name)

  However, you could also write it like this:

      for(
        product <- products,
        product.id in ids,
        do: {product.name, product.price}
      )
      |> List.keysort(1)
      |> Enum.map(&elem(&1, 0))

  Using `List.keysort/3` will be a better choice for performance sensitive
  code as it avoids additional traversals.
  """
  @spec sort_by(
          t,
          (element -> mapped_element),
          (element, element -> boolean) | :asc | :desc | module() | {:asc | :desc, module()}
        ) ::
          list
        when mapped_element: element
  def sort_by(enumerable, mapper, sorter \\ :asc)

  def sort_by(enumerable, mapper, :desc) when is_function(mapper, 1) do
    enumerable
    |> reduce([], &[{&1, mapper.(&1)} | &2])
    |> List.keysort(1, :asc)
    |> List.foldl([], &[elem(&1, 0) | &2])
  end

  def sort_by(enumerable, mapper, sorter) when is_function(mapper, 1) do
    enumerable
    |> map(&{&1, mapper.(&1)})
    |> List.keysort(1, sorter)
    |> map(&elem(&1, 0))
  end

  @doc """
  Splits the `enumerable` into two enumerables, leaving `count`
  elements in the first one.

  If `count` is a negative number, it starts counting from the
  back to the beginning of the `enumerable`.

  Be aware that a negative `count` implies the `enumerable`
  will be enumerated 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(enumerable, count) when is_list(enumerable) and is_integer(count) and count >= 0 do
    split_list(enumerable, count, [])
  end

  def split(enumerable, count) when is_integer(count) and count >= 0 do
    {_, list1, list2} =
      reduce(enumerable, {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(enumerable, count) when is_integer(count) and count < 0 do
    split_reverse_list(reverse(enumerable), -count, [])
  end

  @doc """
  Splits enumerable in two at the position of the element for which
  `fun` returns a falsy value (`false` or `nil`) for the first time.

  It returns a two-element tuple with two lists of elements.
  The element that triggered the split is part of the second list.

  ## Examples

      iex> Enum.split_while([1, 2, 3, 4], fn x -> x < 3 end)
      {[1, 2], [3, 4]}

      iex> Enum.split_while([1, 2, 3, 4], fn x -> x < 0 end)
      {[], [1, 2, 3, 4]}

      iex> Enum.split_while([1, 2, 3, 4], fn x -> x > 0 end)
      {[1, 2, 3, 4], []}

  """
  @spec split_while(t, (element -> as_boolean(term))) :: {list, list}
  def split_while(enumerable, fun) when is_list(enumerable) do
    split_while_list(enumerable, fun, [])
  end

  def split_while(enumerable, fun) do
    {list1, list2} =
      reduce(enumerable, {[], []}, 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 """
  Returns the sum of all elements.

  Raises `ArithmeticError` if `enumerable` contains a non-numeric value.

  If you need to apply a transformation first, consider using `Enum.sum_by/2` instead.

  ## Examples

      iex> Enum.sum([1, 2, 3])
      6

      iex> Enum.sum(1..10)
      55

      iex> Enum.sum(1..10//2)
      25

  """
  @spec sum(t) :: number
  def sum(enumerable)

  def sum(first..last//step = range) do
    range
    |> Range.size()
    |> Kernel.*(first + last - rem(last - first, step))
    |> div(2)
  end

  def sum(enumerable) do
    reduce(enumerable, 0, &+/2)
  end

  @doc """
  Maps and sums the given `enumerable` in one pass.

  Raises `ArithmeticError` if `mapper` returns a non-numeric value.

  ## Examples

      iex> Enum.sum_by([%{count: 1}, %{count: 2}, %{count: 3}], fn x -> x.count end)
      6

      iex> Enum.sum_by(1..3, fn x -> x ** 2 end)
      14

      iex> Enum.sum_by([], fn x -> x.count end)
      0

  Filtering can be achieved by returning `0` to ignore elements:

      iex> Enum.sum_by([1, -2, 3], fn x -> if x > 0, do: x, else: 0 end)
      4

  """
  @doc since: "1.18.0"
  @spec sum_by(t, (element -> number)) :: number
  def sum_by(enumerable, mapper)

  def sum_by(list, mapper) when is_list(list) and is_function(mapper, 1) do
    sum_by_list(list, mapper, 0)
  end

  def sum_by(enumerable, mapper) when is_function(mapper, 1) do
    reduce(enumerable, 0, fn x, acc -> acc + mapper.(x) end)
  end

  @doc """
  Returns the product of all elements.

  Raises `ArithmeticError` if `enumerable` contains a non-numeric value.

  If you need to apply a transformation first, consider using `Enum.product_by/2` instead.

  ## Examples

      iex> Enum.product([])
      1
      iex> Enum.product([2, 3, 4])
      24
      iex> Enum.product([2.0, 3.0, 4.0])
      24.0

  """
  @doc since: "1.12.0"
  @spec product(t) :: number
  def product(enumerable) do
    reduce(enumerable, 1, &*/2)
  end

  @doc """
  Maps and computes the product of the given `enumerable` in one pass.

  Raises `ArithmeticError` if `mapper` returns a non-numeric value.

  ## Examples

      iex> Enum.product_by([%{count: 2}, %{count: 4}, %{count: 3}], fn x -> x.count end)
      24

      iex> Enum.product_by(1..3, fn x -> x ** 2 end)
      36

      iex> Enum.product_by([], fn x -> x.count end)
      1

  Filtering can be achieved by returning `1` to ignore elements:

      iex> Enum.product_by([2, -1, 3], fn x -> if x > 0, do: x, else: 1 end)
      6

  """
  @doc since: "1.18.0"
  @spec product_by(t, (element -> number)) :: number
  def product_by(enumerable, mapper)

  def product_by(list, mapper) when is_list(list) and is_function(mapper, 1) do
    product_by_list(list, mapper, 1)
  end

  def product_by(enumerable, mapper) when is_function(mapper, 1) do
    reduce(enumerable, 1, fn x, acc -> acc * mapper.(x) end)
  end

  @doc """
  Takes an `amount` of elements from the beginning or the end of the `enumerable`.

  If a positive `amount` is given, it takes the `amount` elements from the
  beginning of the `enumerable`.

  If a negative `amount` is given, the `amount` of elements will be taken from the end.
  The `enumerable` will be enumerated once to retrieve the proper index and
  the remaining calculation is performed from the end.

  If amount is `0`, it returns `[]`.

  ## 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)
      [3]

  """
  @spec take(t, integer) :: list
  def take(enumerable, amount)

  def take(_enumerable, 0), do: []

  def take(enumerable, amount)
      when is_list(enumerable) and is_integer(amount) and amount > 0 do
    take_list(enumerable, amount)
  end

  def take(enumerable, amount) when is_integer(amount) and amount > 0 do
    {_, {res, _}} =
      Enumerable.reduce(enumerable, {:cont, {[], amount}}, fn entry, {list, n} ->
        case n do
          1 -> {:halt, {[entry | list], n - 1}}
          _ -> {:cont, {[entry | list], n - 1}}
        end
      end)

    :lists.reverse(res)
  end

  def take(enumerable, amount) when is_integer(amount) and amount < 0 do
    case slice_count_and_fun(enumerable, 1) do
      {0, _fun} ->
        []

      {count, fun} ->
        first = Kernel.max(amount + count, 0)
        fun.(first, count - first, 1)
    end
  end

  @doc """
  Returns a list of every `nth` element in the `enumerable`,
  starting with the first element.

  The first element is always included, unless `nth` is 0.

  The second argument specifying every `nth` element must be a non-negative
  integer.

  ## Examples

      iex> Enum.take_every(1..10, 2)
      [1, 3, 5, 7, 9]

      iex> Enum.take_every(1..10, 0)
      []

      iex> Enum.take_every([1, 2, 3], 1)
      [1, 2, 3]

  """
  @spec take_every(t, non_neg_integer) :: list
  def take_every(enumerable, nth)

  def take_every(_enumerable, 0), do: []
  def take_every(enumerable, 1), do: to_list(enumerable)

  def take_every(list, nth) when is_list(list) and is_integer(nth) and nth > 1 do
    take_every_list(list, nth - 1)
  end

  def take_every(enumerable, nth) when is_integer(nth) and nth > 1 do
    {res, _} = reduce(enumerable, {[], :first}, R.take_every(nth))
    :lists.reverse(res)
  end

  @doc """
  Takes `count` random elements from `enumerable`.

  Note that this function will traverse the whole `enumerable` to
  get the random sublist.

  See `random/1` for notes on implementation and random seed.

  ## Examples

      # Although not necessary, let's seed the random algorithm
      iex> :rand.seed(:exsss, {1, 2, 3})
      iex> Enum.take_random(1..10, 2)
      [6, 1]
      iex> Enum.take_random(?a..?z, 5)
      ~c"bkzmt"

  """
  @spec take_random(t, non_neg_integer) :: list
  def take_random(enumerable, count)
  def take_random(_enumerable, 0), do: []
  def take_random([], _), do: []

  def take_random(enumerable, 1) do
    enumerable
    |> reduce({0, 0, 1.0, nil}, fn
      elem, {idx, idx, w, _current} ->
        {jdx, w} = take_jdx_w(idx, w, 1)
        {idx + 1, jdx, w, elem}

      _elem, {idx, jdx, w, current} ->
        {idx + 1, jdx, w, current}
    end)
    |> case do
      {0, 0, 1.0, nil} -> []
      {_idx, _jdx, _w, current} -> [current]
    end
  end

  def take_random(enumerable, count) when count in 0..128 do
    sample = Tuple.duplicate(nil, count)

    reducer = fn
      elem, {idx, jdx, w, sample} when idx < count ->
        rand = take_index(idx)
        sample = sample |> put_elem(idx, elem(sample, rand)) |> put_elem(rand, elem)

        if idx == jdx do
          {jdx, w} = take_jdx_w(idx, w, count)
          {idx + 1, jdx, w, sample}
        else
          {idx + 1, jdx, w, sample}
        end

      elem, {idx, idx, w, sample} ->
        pos = :rand.uniform(count) - 1
        {jdx, w} = take_jdx_w(idx, w, count)
        {idx + 1, jdx, w, put_elem(sample, pos, elem)}

      _elem, {idx, jdx, w, sample} ->
        {idx + 1, jdx, w, sample}
    end

    {size, _, _, sample} = reduce(enumerable, {0, count - 1, 1.0, sample}, reducer)

    if count < size do
      Tuple.to_list(sample)
    else
      take_tupled(sample, size, [])
    end
  end

  def take_random(enumerable, count) when is_integer(count) and count >= 0 do
    reducer = fn
      elem, {idx, jdx, w, sample} when idx < count ->
        rand = take_index(idx)
        sample = sample |> Map.put(idx, Map.get(sample, rand)) |> Map.put(rand, elem)

        if idx == jdx do
          {jdx, w} = take_jdx_w(idx, w, count)
          {idx + 1, jdx, w, sample}
        else
          {idx + 1, jdx, w, sample}
        end

      elem, {idx, idx, w, sample} ->
        pos = :rand.uniform(count) - 1
        {jdx, w} = take_jdx_w(idx, w, count)
        {idx + 1, jdx, w, %{sample | pos => elem}}

      _elem, {idx, jdx, w, sample} ->
        {idx + 1, jdx, w, sample}
    end

    {size, _, _, sample} = reduce(enumerable, {0, count - 1, 1.0, %{}}, reducer)
    take_mapped(sample, Kernel.min(count, size), [])
  end

  @compile {:inline, take_jdx_w: 3, take_index: 1}
  defp take_jdx_w(idx, w, count) do
    w = w * :math.exp(:math.log(:rand.uniform()) / count)
    jdx = idx + floor(:math.log(:rand.uniform()) / :math.log(1 - w)) + 1
    {jdx, w}
  end

  defp take_index(0), do: 0
  defp take_index(idx), do: :rand.uniform(idx + 1) - 1

  defp take_tupled(_sample, 0, acc), do: acc

  defp take_tupled(sample, position, acc) do
    position = position - 1
    take_tupled(sample, position, [elem(sample, position) | acc])
  end

  defp take_mapped(_sample, 0, acc), do: acc

  defp take_mapped(sample, position, acc) do
    position = position - 1
    take_mapped(sample, position, [Map.fetch!(sample, position) | acc])
  end

  @doc """
  Takes the elements from the beginning of the `enumerable` while `fun` returns
  a truthy value.

  ## 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(enumerable, fun) when is_list(enumerable) do
    take_while_list(enumerable, fun)
  end

  def take_while(enumerable, fun) do
    {_, res} =
      Enumerable.reduce(enumerable, {:cont, []}, fn entry, acc ->
        if fun.(entry) do
          {:cont, [entry | acc]}
        else
          {:halt, acc}
        end
      end)

    :lists.reverse(res)
  end

  @doc """
  Converts `enumerable` to a list.

  ## Examples

      iex> Enum.to_list(1..3)
      [1, 2, 3]

  """
  @spec to_list(t) :: [element]
  def to_list(enumerable) when is_list(enumerable), do: enumerable
  def to_list(%{__struct__: Range} = range), do: Range.to_list(range)
  def to_list(%_{} = enumerable), do: reverse(enumerable) |> :lists.reverse()
  def to_list(%{} = enumerable), do: Map.to_list(enumerable)
  def to_list(enumerable), do: reverse(enumerable) |> :lists.reverse()

  @doc """
  Enumerates the `enumerable`, removing all duplicate elements.

  The first occurrence of each element is kept and all following
  duplicates are removed. The overall order is preserved.

  ## Examples

      iex> Enum.uniq([1, 2, 3, 3, 2, 1])
      [1, 2, 3]

  """
  @spec uniq(t) :: list
  def uniq(enumerable) do
    uniq_by(enumerable, fn x -> x end)
  end

  @doc false
  @deprecated "Use Enum.uniq_by/2 instead"
  def uniq(enumerable, fun) do
    uniq_by(enumerable, fun)
  end

  @doc """
  Enumerates the `enumerable`, by removing the elements for which
  function `fun` returned duplicate elements.

  The function `fun` maps every element to a term. Two elements are
  considered duplicates if the return value of `fun` is equal for
  both of them.

  The first occurrence of each element is kept and all following
  duplicates are removed. The overall order is preserved.

  ## Example

      iex> Enum.uniq_by([{1, :x}, {2, :y}, {1, :z}], fn {x, _} -> x end)
      [{1, :x}, {2, :y}]

      iex> Enum.uniq_by([a: {:tea, 2}, b: {:tea, 2}, c: {:coffee, 1}], fn {_, y} -> y end)
      [a: {:tea, 2}, c: {:coffee, 1}]

  """
  @spec uniq_by(t, (element -> term)) :: list

  def uniq_by(enumerable, fun) when is_list(enumerable) do
    uniq_list(enumerable, %{}, fun)
  end

  def uniq_by(enumerable, fun) do
    {list, _} = reduce(enumerable, {[], %{}}, R.uniq_by(fun))
    :lists.reverse(list)
  end

  @doc """
  Opposite of `zip/2`. Extracts two-element tuples from the
  given `enumerable` and groups them together.

  It takes an `enumerable` with elements being two-element tuples and returns
  a tuple with two lists, each of which is formed by the first and
  second element of each tuple, respectively.

  This function fails unless `enumerable` is or can be converted into a
  list of tuples with *exactly* two elements in each tuple.

  ## Examples

      iex> Enum.unzip([{:a, 1}, {:b, 2}, {:c, 3}])
      {[:a, :b, :c], [1, 2, 3]}

  """
  @spec unzip(t) :: {[element], [element]}
  def unzip(enumerable)

  def unzip([_ | _] = list) do
    :lists.reverse(list) |> unzip([], [])
  end

  def unzip([]) do
    {[], []}
  end

  def unzip(enumerable) do
    {list1, list2} =
      reduce(enumerable, {[], []}, fn {el1, el2}, {list1, list2} ->
        {[el1 | list1], [el2 | list2]}
      end)

    {:lists.reverse(list1), :lists.reverse(list2)}
  end

  defp unzip([{el1, el2} | reversed_list], list1, list2) do
    unzip(reversed_list, [el1 | list1], [el2 | list2])
  end

  defp unzip([], list1, list2) do
    {list1, list2}
  end

  @doc """
  Returns the `enumerable` with each element wrapped in a tuple
  alongside its index or according to a given function.

  If an integer offset is given as `fun_or_offset`, it will index from the given
  offset instead of from zero.

  If a 2-arity function is given as `fun_or_offset`, the function will be invoked
  for each element in `enumerable` as the first argument and with a zero-based
  index as the second. `with_index/2` returns a list with the result of each invocation.

  ## Examples

      iex> Enum.with_index([:a, :b, :c])
      [a: 0, b: 1, c: 2]

      iex> Enum.with_index([:a, :b, :c], 3)
      [a: 3, b: 4, c: 5]

      iex> Enum.with_index([:a, :b, :c], fn element, index -> {index, element} end)
      [{0, :a}, {1, :b}, {2, :c}]

  """
  @spec with_index(t, integer) :: [{term, integer}]
  @spec with_index(t, (element, index -> value)) :: [value] when value: any
  def with_index(enumerable, fun_or_offset \\ 0)

  def with_index(enumerable, offset) when is_list(enumerable) and is_integer(offset) do
    with_index_list(enumerable, offset)
  end

  def with_index(enumerable, fun) when is_list(enumerable) and is_function(fun, 2) do
    with_index_list(enumerable, 0, fun)
  end

  def with_index(enumerable, offset) when is_integer(offset) do
    enumerable
    |> map_reduce(offset, fn x, i -> {{x, i}, i + 1} end)
    |> elem(0)
  end

  def with_index(enumerable, fun) when is_function(fun, 2) do
    enumerable
    |> map_reduce(0, fn x, i -> {fun.(x, i), i + 1} end)
    |> elem(0)
  end

  @doc """
  Zips corresponding elements from two enumerables into a list
  of tuples.

  Because a list of two-element tuples with atoms as the first
  tuple element is a keyword list (`Keyword`), zipping a first list
  of atoms with a second list of any kind creates a keyword list.

  The zipping finishes as soon as either enumerable completes.

  ## Examples

      iex> Enum.zip([1, 2, 3], [:a, :b, :c])
      [{1, :a}, {2, :b}, {3, :c}]

      iex> Enum.zip([:a, :b, :c], [1, 2, 3])
      [a: 1, b: 2, c: 3]

      iex> Enum.zip([1, 2, 3, 4, 5], [:a, :b, :c])
      [{1, :a}, {2, :b}, {3, :c}]

  """
  @spec zip(t, t) :: [{any, any}]
  def zip(enumerable1, enumerable2) when is_list(enumerable1) and is_list(enumerable2) do
    zip_list(enumerable1, enumerable2, [])
  end

  def zip(enumerable1, enumerable2) do
    zip([enumerable1, enumerable2])
  end

  @doc """
  Zips corresponding elements from a finite collection of enumerables
  into a list of tuples.

  The zipping finishes as soon as any enumerable in the given collection completes.

  ## Examples

      iex> Enum.zip([[1, 2, 3], [:a, :b, :c], ["foo", "bar", "baz"]])
      [{1, :a, "foo"}, {2, :b, "bar"}, {3, :c, "baz"}]

      iex> Enum.zip([[1, 2, 3, 4, 5], [:a, :b, :c]])
      [{1, :a}, {2, :b}, {3, :c}]

  """
  @doc since: "1.4.0"
  @spec zip(enumerables) :: [tuple()] when enumerables: [t()] | t()
  def zip([]), do: []

  def zip(enumerables) do
    zip_reduce(enumerables, [], &[List.to_tuple(&1) | &2])
    |> :lists.reverse()
  end

  @doc """
  Zips corresponding elements from two enumerables into a list, transforming them with
  the `zip_fun` function as it goes.

  The corresponding elements from each collection are passed to the provided two-arity `zip_fun`
  function in turn. Returns a list that contains the result of calling `zip_fun` for each pair of
  elements.

  The zipping finishes as soon as either enumerable runs out of elements.

  ## Zipping Maps

  It's important to remember that zipping inherently relies on order.
  If you zip two lists you get the element at the index from each list in turn.
  If we zip two maps together it's tempting to think that you will get the given
  key in the left map and the matching key in the right map, but there is no such
  guarantee because map keys are not ordered! Consider the following:

      left = %{:a => 1, 1 => 3}
      right = %{:a => 1, :b => :c}
      Enum.zip(left, right)
      #=> [{{1, 3}, {:a, 1}}, {{:a, 1}, {:b, :c}}]

  As you can see `:a` does not get paired with `:a`. If this is what you want,
  you should use `Map.merge/3`.

  ## Examples

      iex> Enum.zip_with([1, 2], [3, 4], fn x, y -> x + y end)
      [4, 6]

      iex> Enum.zip_with([1, 2], [3, 4, 5, 6], fn x, y -> x + y end)
      [4, 6]

      iex> Enum.zip_with([1, 2, 5, 6], [3, 4], fn x, y -> x + y end)
      [4, 6]

  """
  @doc since: "1.12.0"
  @spec zip_with(t, t, (enum1_elem :: term, enum2_elem :: term -> term)) :: [term]
  def zip_with(enumerable1, enumerable2, zip_fun)
      when is_list(enumerable1) and is_list(enumerable2) and is_function(zip_fun, 2) do
    zip_with_list(enumerable1, enumerable2, zip_fun)
  end

  def zip_with(enumerable1, enumerable2, zip_fun) when is_function(zip_fun, 2) do
    zip_reduce(enumerable1, enumerable2, [], fn l, r, acc -> [zip_fun.(l, r) | acc] end)
    |> :lists.reverse()
  end

  @doc """
  Zips corresponding elements from a finite collection of enumerables
  into list, transforming them with the `zip_fun` function as it goes.

  The first element from each of the enums in `enumerables` will be put
  into a list which is then passed to the one-arity `zip_fun` function.
  Then, the second elements from each of the enums are put into a list
  and passed to `zip_fun`, and so on until any one of the enums in
  `enumerables` runs out of elements.

  Returns a list with all the results of calling `zip_fun`.

  ## Examples

      iex> Enum.zip_with([[1, 2], [3, 4], [5, 6]], fn [x, y, z] -> x + y + z end)
      [9, 12]

      iex> Enum.zip_with([[1, 2], [3, 4]], fn [x, y] -> x + y end)
      [4, 6]

  `zip_with/2` can be used to transpose lists of lists:

      iex> Enum.zip_with([[1, 2,], [3, 4]], & &1)
      [[1, 3], [2, 4]]

  """
  @doc since: "1.12.0"
  @spec zip_with(t, ([term] -> term)) :: [term]
  def zip_with([], _fun), do: []

  def zip_with(enumerables, zip_fun) do
    zip_reduce(enumerables, [], fn values, acc -> [zip_fun.(values) | acc] end)
    |> :lists.reverse()
  end

  @doc """
  Reduces over two enumerables halting as soon as either enumerable is empty.

  In practice, the behavior provided by this function can be achieved with:

      Enum.reduce(Stream.zip(left, right), acc, reducer)

  But `zip_reduce/4` exists for convenience purposes.

  ## Examples

      iex> Enum.zip_reduce([1, 2], [3, 4], 0, fn x, y, acc -> x + y + acc end)
      10

  If one of the lists has more entries than the others,
  those entries are discarded:

      iex> Enum.zip_reduce([1, 2, 3], [4, 5], [], fn x, y, acc -> [x + y | acc] end)
      [7, 5]
  """
  @doc since: "1.12.0"
  @spec zip_reduce(t, t, acc, (enum1_elem :: term, enum2_elem :: term, acc -> acc)) :: acc
        when acc: term
  def zip_reduce(left, right, acc, reducer)
      when is_list(left) and is_list(right) and is_function(reducer, 3) do
    zip_reduce_list(left, right, acc, reducer)
  end

  def zip_reduce(left, right, acc, reducer) when is_function(reducer, 3) do
    reduce = fn [l, r], acc -> {:cont, reducer.(l, r, acc)} end
    R.zip_with([left, right], & &1).({:cont, acc}, reduce) |> elem(1)
  end

  @doc """
  Reduces over all of the given enumerables, halting as soon as any enumerable is
  empty.

  The reducer will receive 2 args: a list of elements (one from each enum) and the
  accumulator.

  In practice, the behavior provided by this function can be achieved with:

      Enum.reduce(Stream.zip(enums), acc, reducer)

  But `zip_reduce/3` exists for convenience purposes.

  ## Examples

      iex> enums = [[1, 1], [2, 2], [3, 3]]
      ...>  Enum.zip_reduce(enums, [], fn elements, acc ->
      ...>    [List.to_tuple(elements) | acc]
      ...> end)
      [{1, 2, 3}, {1, 2, 3}]

  If one of the lists has more entries than the others,
  those entries are discarded:

      iex> enums = [[1, 2], [a: 3, b: 4], [5, 6, 7]]
      ...> Enum.zip_reduce(enums, [], fn elements, acc ->
      ...>   [List.to_tuple(elements) | acc]
      ...> end)
      [{2, {:b, 4}, 6}, {1, {:a, 3}, 5}]
  """
  @doc since: "1.12.0"
  @spec zip_reduce(t, acc, ([term], acc -> acc)) :: acc when acc: term
  def zip_reduce([], acc, reducer) when is_function(reducer, 2), do: acc

  def zip_reduce(enumerables, acc, reducer) when is_function(reducer, 2) do
    R.zip_with(enumerables, & &1).({:cont, acc}, &{:cont, reducer.(&1, &2)}) |> elem(1)
  end

  ## Helpers

  @compile {:inline,
            entry_to_string: 1,
            reduce: 3,
            reduce_by: 3,
            reduce_enumerable: 3,
            reduce_range: 5,
            map_range: 4}

  defp entry_to_string(entry) when is_binary(entry), do: entry
  defp entry_to_string(entry), do: String.Chars.to_string(entry)

  defp aggregate([head | tail], fun, _empty) do
    aggregate_list(tail, head, fun)
  end

  defp aggregate([], _fun, empty) do
    empty.()
  end

  defp aggregate(first..last//step = range, fun, empty) do
    case Range.size(range) do
      0 ->
        empty.()

      _ ->
        last = last - rem(last - first, step)

        case fun.(first, last) do
          true -> first
          false -> last
        end
    end
  end

  defp aggregate(enumerable, fun, empty) do
    ref = make_ref()

    enumerable
    |> reduce(ref, fn
      element, ^ref ->
        element

      element, acc ->
        case fun.(acc, element) do
          true -> acc
          false -> element
        end
    end)
    |> case do
      ^ref -> empty.()
      result -> result
    end
  end

  defp aggregate_list([head | tail], acc, fun) do
    acc =
      case fun.(acc, head) do
        true -> acc
        false -> head
      end

    aggregate_list(tail, acc, fun)
  end

  defp aggregate_list([], acc, _fun), do: acc

  defp aggregate_by(enumerable, fun, sorter, empty_fallback) do
    first_fun = &[&1 | fun.(&1)]

    reduce_fun = fn entry, [_ | fun_ref] = old ->
      fun_entry = fun.(entry)

      case sorter.(fun_ref, fun_entry) do
        true -> old
        false -> [entry | fun_entry]
      end
    end

    case reduce_by(enumerable, first_fun, reduce_fun) do
      :empty -> empty_fallback.()
      [entry | _] -> entry
    end
  end

  defp reduce_by([head | tail], first, fun) do
    :lists.foldl(fun, first.(head), tail)
  end

  defp reduce_by([], _first, _fun) do
    :empty
  end

  defp reduce_by(enumerable, first, fun) do
    reduce(enumerable, :empty, fn
      element, :empty -> first.(element)
      element, acc -> fun.(element, acc)
    end)
  end

  ## Implementations

  ## all?/1

  defp all_list([h | t]) do
    if h do
      all_list(t)
    else
      false
    end
  end

  defp all_list([]) do
    true
  end

  ## any?/1

  defp any_list([h | t]) do
    if h do
      true
    else
      any_list(t)
    end
  end

  defp any_list([]) do
    false
  end

  ## any?/2 all?/2

  defp predicate_list([h | t], initial, fun) do
    if !!fun.(h) == initial do
      predicate_list(t, initial, fun)
    else
      not initial
    end
  end

  defp predicate_list([], initial, _) do
    initial
  end

  defp predicate_range(first, last, step, initial, fun)
       when step > 0 and first <= last
       when step < 0 and first >= last do
    if !!fun.(first) == initial do
      predicate_range(first + step, last, step, initial, fun)
    else
      not initial
    end
  end

  defp predicate_range(_first, _last, _step, initial, _fun) do
    initial
  end

  ## concat

  defp concat_list([h | t]) when is_list(h), do: h ++ concat_list(t)
  defp concat_list([h | t]), do: concat_enum([h | t])
  defp concat_list([]), do: []

  defp concat_enum(enum) do
    fun = &[&1 | &2]
    enum |> reduce([], &reduce(&1, &2, fun)) |> :lists.reverse()
  end

  # count_until

  @compile {:inline, count_until_list: 3}

  defp count_until_list([], _limit, acc), do: acc

  defp count_until_list([_head | tail], limit, acc) do
    case acc + 1 do
      ^limit -> limit
      acc -> count_until_list(tail, limit, acc)
    end
  end

  defp count_until_enum(enumerable, limit) do
    case Enumerable.count(enumerable) do
      {:ok, value} ->
        Kernel.min(value, limit)

      {:error, module} ->
        module.reduce(enumerable, {:cont, 0}, fn _entry, acc ->
          case acc + 1 do
            ^limit -> {:halt, limit}
            acc -> {:cont, acc}
          end
        end)
        |> elem(1)
    end
  end

  @compile {:inline, count_until_list: 4}

  defp count_until_list([], _fun, _limit, acc), do: acc

  defp count_until_list([head | tail], fun, limit, acc) do
    if fun.(head) do
      case acc + 1 do
        ^limit -> limit
        acc -> count_until_list(tail, fun, limit, acc)
      end
    else
      count_until_list(tail, fun, limit, acc)
    end
  end

  defp count_until_enum(enumerable, fun, limit) do
    Enumerable.reduce(enumerable, {:cont, 0}, fn entry, acc ->
      if fun.(entry) do
        case acc + 1 do
          ^limit -> {:halt, limit}
          acc -> {:cont, acc}
        end
      else
        {:cont, acc}
      end
    end)
    |> elem(1)
  end

  # dedup

  defp dedup_list([value | [value | _] = tail]), do: dedup_list(tail)
  defp dedup_list([value | tail]), do: [value | dedup_list(tail)]
  defp dedup_list([]), do: []

  ## drop

  defp drop_list(list, 0), do: list
  defp drop_list([_ | tail], counter), do: drop_list(tail, counter - 1)
  defp drop_list([], _), do: []

  ## drop_while

  defp drop_while_list([head | tail], fun) do
    if fun.(head) do
      drop_while_list(tail, fun)
    else
      [head | tail]
    end
  end

  defp drop_while_list([], _) do
    []
  end

  ## filter

  defp filter_list([head | tail], fun) do
    if fun.(head) do
      [head | filter_list(tail, fun)]
    else
      filter_list(tail, fun)
    end
  end

  defp filter_list([], _fun) do
    []
  end

  ## find

  defp find_list([head | tail], default, fun) do
    if fun.(head) do
      head
    else
      find_list(tail, default, fun)
    end
  end

  defp find_list([], default, _) do
    default
  end

  ## find_index

  defp find_index_list([head | tail], counter, fun) do
    if fun.(head) do
      counter
    else
      find_index_list(tail, counter + 1, fun)
    end
  end

  defp find_index_list([], _, _) do
    nil
  end

  ## find_value

  defp find_value_list([head | tail], default, fun) do
    fun.(head) || find_value_list(tail, default, fun)
  end

  defp find_value_list([], default, _) do
    default
  end

  ## flat_map

  defp flat_map_list([head | tail], fun) do
    case fun.(head) do
      # the two first clauses are an optimization
      [] -> flat_map_list(tail, fun)
      [elem] -> [elem | flat_map_list(tail, fun)]
      list when is_list(list) -> list ++ flat_map_list(tail, fun)
      other -> to_list(other) ++ flat_map_list(tail, fun)
    end
  end

  defp flat_map_list([], _fun) do
    []
  end

  ## intersperse

  defp intersperse_non_empty_list([head], _separator), do: [head]

  defp intersperse_non_empty_list([head | rest], separator) do
    [head, separator | intersperse_non_empty_list(rest, separator)]
  end

  ## join

  defp join_list([], _joiner), do: ""

  defp join_list(list, joiner) do
    join_non_empty_list(list, joiner, [])
    |> :lists.reverse()
    |> IO.iodata_to_binary()
  end

  defp join_non_empty_list([first], _joiner, acc), do: [entry_to_string(first) | acc]

  defp join_non_empty_list([first | rest], joiner, acc) do
    join_non_empty_list(rest, joiner, [joiner, entry_to_string(first) | acc])
  end

  ## map

  defp map_range(first, last, step, fun)
       when step > 0 and first <= last
       when step < 0 and first >= last do
    [fun.(first) | map_range(first + step, last, step, fun)]
  end

  defp map_range(_first, _last, _step, _fun) do
    []
  end

  ## map_intersperse

  defp map_intersperse_list([], _, _),
    do: []

  defp map_intersperse_list([last], _, mapper),
    do: [mapper.(last)]

  defp map_intersperse_list([head | rest], separator, mapper),
    do: [mapper.(head), separator | map_intersperse_list(rest, separator, mapper)]

  ## reduce

  defp reduce_range(first, last, step, acc, fun)
       when step > 0 and first <= last
       when step < 0 and first >= last do
    reduce_range(first + step, last, step, fun.(first, acc), fun)
  end

  defp reduce_range(_first, _last, _step, acc, _fun) do
    acc
  end

  defp reduce_enumerable(enumerable, acc, fun) do
    Enumerable.reduce(enumerable, {:cont, acc}, fn x, acc -> {:cont, fun.(x, acc)} end) |> elem(1)
  end

  ## reject

  defp reject_list([head | tail], fun) do
    if fun.(head) do
      reject_list(tail, fun)
    else
      [head | reject_list(tail, fun)]
    end
  end

  defp reject_list([], _fun) do
    []
  end

  ## reverse_slice

  defp reverse_slice(rest, idx, idx, count, acc) do
    {slice, rest} = head_slice(rest, count, [])
    :lists.reverse(rest, :lists.reverse(slice, acc))
  end

  defp reverse_slice([elem | rest], idx, start, count, acc) do
    reverse_slice(rest, idx - 1, start, count, [elem | acc])
  end

  defp head_slice(rest, 0, acc), do: {acc, rest}

  defp head_slice([elem | rest], count, acc) do
    head_slice(rest, count - 1, [elem | acc])
  end

  ## scan

  defp scan_list([], _acc, _fun), do: []

  defp scan_list([elem | rest], acc, fun) do
    acc = fun.(elem, acc)
    [acc | scan_list(rest, acc, fun)]
  end

  ## slice

  defp slice_forward(enumerable, start, amount, step) when start < 0 do
    {count, fun} = slice_count_and_fun(enumerable, step)
    start = count + start

    if start >= 0 do
      amount = Kernel.min(amount, count - start)
      amount = amount_with_step(amount, step)
      fun.(start, amount, step)
    else
      []
    end
  end

  defp slice_forward(list, start, amount, step) when is_list(list) do
    amount = amount_with_step(amount, step)
    slice_list(list, start, amount, step)
  end

  defp slice_forward(enumerable, start, amount, step) do
    case Enumerable.slice(enumerable) do
      {:ok, count, _} when start >= count ->
        []

      {:ok, count, fun} when is_function(fun, 1) ->
        amount = Kernel.min(amount, count - start) |> amount_with_step(step)
        enumerable |> fun.() |> slice_exact(start, amount, step, count)

      {:ok, count, fun} when is_function(fun, 3) ->
        amount = Kernel.min(amount, count - start) |> amount_with_step(step)
        fun.(start, amount, step)

      # TODO: Remove me on v2.0
      {:ok, count, fun} when is_function(fun, 2) ->
        IO.warn(
          "#{inspect(Enumerable.impl_for(enumerable))} must return a three arity function on slice/1"
        )

        amount = Kernel.min(amount, count - start)

        if step == 1 do
          fun.(start, amount)
        else
          fun.(start, Kernel.min(amount * step, count - start))
          |> take_every_list(amount, step - 1)
        end

      {:error, module} ->
        slice_enum(enumerable, module, start, amount, step)
    end
  end

  defp slice_list(list, start, amount, step) do
    if step == 1 do
      list |> drop_list(start) |> take_list(amount)
    else
      list |> drop_list(start) |> take_every_list(amount, step - 1)
    end
  end

  defp slice_enum(enumerable, module, start, amount, 1) do
    {_, {_, _, slice}} =
      module.reduce(enumerable, {:cont, {start, amount, []}}, fn
        _entry, {start, amount, _list} when start > 0 ->
          {:cont, {start - 1, amount, []}}

        entry, {start, amount, list} when amount > 1 ->
          {:cont, {start, amount - 1, [entry | list]}}

        entry, {start, amount, list} ->
          {:halt, {start, amount, [entry | list]}}
      end)

    :lists.reverse(slice)
  end

  defp slice_enum(enumerable, module, start, amount, step) do
    {_, {_, _, _, slice}} =
      module.reduce(enumerable, {:cont, {start, amount, 1, []}}, fn
        _entry, {start, amount, to_drop, _list} when start > 0 ->
          {:cont, {start - 1, amount, to_drop, []}}

        entry, {start, amount, to_drop, list} when amount > 1 ->
          case to_drop do
            1 -> {:cont, {start, amount - 1, step, [entry | list]}}
            _ -> {:cont, {start, amount - 1, to_drop - 1, list}}
          end

        entry, {start, amount, to_drop, list} ->
          case to_drop do
            1 -> {:halt, {start, amount, to_drop, [entry | list]}}
            _ -> {:halt, {start, amount, to_drop, list}}
          end
      end)

    :lists.reverse(slice)
  end

  defp slice_count_and_fun(list, _step) when is_list(list) do
    length = length(list)
    {length, &slice_exact(list, &1, &2, &3, length)}
  end

  defp slice_count_and_fun(enumerable, step) do
    case Enumerable.slice(enumerable) do
      {:ok, count, fun} when is_function(fun, 1) ->
        {count, &slice_exact(fun.(enumerable), &1, &2, &3, count)}

      {:ok, count, fun} when is_function(fun, 3) ->
        {count, fun}

      # TODO: Remove me on v2.0
      {:ok, count, fun} when is_function(fun, 2) ->
        IO.warn(
          "#{inspect(Enumerable.impl_for(enumerable))} must return a three arity function on slice/1"
        )

        if step == 1 do
          {count, fn start, amount, 1 -> fun.(start, amount) end}
        else
          {count,
           fn start, amount, step ->
             fun.(start, Kernel.min(amount * step, count - start))
             |> take_every_list(amount, step - 1)
           end}
        end

      {:error, module} ->
        {list, count} =
          enumerable
          |> module.reduce({:cont, {[], 0}}, fn elem, {acc, count} ->
            {:cont, {[elem | acc], count + 1}}
          end)
          |> elem(1)

        {count,
         fn start, amount, step ->
           list |> :lists.reverse() |> slice_exact(start, amount, step, count)
         end}
    end
  end

  # Slice a list when we know the bounds
  defp slice_exact(_list, _start, 0, _step, _), do: []

  defp slice_exact(list, start, amount, 1, size) when start + amount == size,
    do: list |> drop_exact(start)

  defp slice_exact(list, start, amount, 1, _),
    do: list |> drop_exact(start) |> take_exact(amount)

  defp slice_exact(list, start, amount, step, _),
    do: list |> drop_exact(start) |> take_every_list(amount, step - 1)

  defp drop_exact(list, 0), do: list
  defp drop_exact([_ | tail], amount), do: drop_exact(tail, amount - 1)

  defp take_exact(_list, 0), do: []
  defp take_exact([head | tail], amount), do: [head | take_exact(tail, amount - 1)]

  ## 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_merge1(t1, h2, t2, [], fun, false) | acc], fun, true)

  defp sort_merge([[h2 | t2], t1 | l], acc, fun, false),
    do: sort_merge(l, [sort_merge1(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_merge1(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_merge1(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_merge1([h1 | t1], h2, t2, m, fun, bool) do
    if fun.(h1, h2) == bool do
      sort_merge2(h1, t1, t2, [h2 | m], fun, bool)
    else
      sort_merge1(t1, h2, t2, [h1 | m], fun, bool)
    end
  end

  defp sort_merge1([], h2, t2, m, _fun, _bool), do: :lists.reverse(t2, [h2 | m])

  defp sort_merge2(h1, t1, [h2 | t2], m, fun, bool) do
    if fun.(h1, h2) == bool do
      sort_merge2(h1, t1, t2, [h2 | m], fun, bool)
    else
      sort_merge1(t1, h2, t2, [h1 | m], fun, bool)
    end
  end

  defp sort_merge2(h1, t1, [], m, _fun, _bool), do: :lists.reverse(t1, [h1 | m])

  ## split

  defp split_list([head | tail], counter, acc) when counter > 0 do
    split_list(tail, counter - 1, [head | acc])
  end

  defp split_list(list, 0, acc) do
    {:lists.reverse(acc), list}
  end

  defp split_list([], _, acc) do
    {:lists.reverse(acc), []}
  end

  defp split_reverse_list([head | tail], counter, acc) when counter > 0 do
    split_reverse_list(tail, counter - 1, [head | acc])
  end

  defp split_reverse_list(list, 0, acc) do
    {:lists.reverse(list), acc}
  end

  defp split_reverse_list([], _, acc) do
    {[], acc}
  end

  ## split_while

  defp split_while_list([head | tail], fun, acc) do
    if fun.(head) do
      split_while_list(tail, fun, [head | acc])
    else
      {:lists.reverse(acc), [head | tail]}
    end
  end

  defp split_while_list([], _, acc) do
    {:lists.reverse(acc), []}
  end

  ## sum_by

  defp sum_by_list([], _, acc), do: acc
  defp sum_by_list([h | t], mapper, acc), do: sum_by_list(t, mapper, acc + mapper.(h))

  ## product_by

  defp product_by_list([], _, acc), do: acc
  defp product_by_list([h | t], mapper, acc), do: product_by_list(t, mapper, acc * mapper.(h))

  ## take

  defp take_list(_list, 0), do: []
  defp take_list([head | tail], counter), do: [head | take_list(tail, counter - 1)]
  defp take_list([], _counter), do: []

  defp take_every_list([head | tail], to_drop),
    do: [head | tail |> drop_list(to_drop) |> take_every_list(to_drop)]

  defp take_every_list([], _to_drop), do: []

  defp take_every_list(_list, 0, _to_drop), do: []

  defp take_every_list([head | tail], counter, to_drop),
    do: [head | tail |> drop_list(to_drop) |> take_every_list(counter - 1, to_drop)]

  defp take_every_list([], _counter, _to_drop), do: []

  ## take_while

  defp take_while_list([head | tail], fun) do
    if fun.(head) do
      [head | take_while_list(tail, fun)]
    else
      []
    end
  end

  defp take_while_list([], _) do
    []
  end

  ## uniq

  defp uniq_list([head | tail], set, fun) do
    value = fun.(head)

    case set do
      %{^value => true} -> uniq_list(tail, set, fun)
      %{} -> [head | uniq_list(tail, Map.put(set, value, true), fun)]
    end
  end

  defp uniq_list([], _set, _fun) do
    []
  end

  ## with_index

  defp with_index_list([head | tail], offset) do
    [{head, offset} | with_index_list(tail, offset + 1)]
  end

  defp with_index_list([], _offset), do: []

  defp with_index_list([head | tail], offset, fun) do
    [fun.(head, offset) | with_index_list(tail, offset + 1, fun)]
  end

  defp with_index_list([], _offset, _fun), do: []

  ## zip

  defp zip_list([head1 | next1], [head2 | next2], acc) do
    zip_list(next1, next2, [{head1, head2} | acc])
  end

  defp zip_list([], _, acc), do: :lists.reverse(acc)
  defp zip_list(_, [], acc), do: :lists.reverse(acc)

  defp zip_with_list([head1 | next1], [head2 | next2], fun) do
    [fun.(head1, head2) | zip_with_list(next1, next2, fun)]
  end

  defp zip_with_list(_, [], _fun), do: []
  defp zip_with_list([], _, _fun), do: []

  defp zip_reduce_list([head1 | next1], [head2 | next2], acc, fun) do
    zip_reduce_list(next1, next2, fun.(head1, head2, acc), fun)
  end

  defp zip_reduce_list(_, [], acc, _fun), do: acc
  defp zip_reduce_list([], _, acc, _fun), do: acc
end

defimpl Enumerable, for: List do
  def count(list), do: {:ok, length(list)}

  def member?(list, value), do: {:ok, :lists.member(value, list)}

  def slice([]), do: {:ok, 0, fn _, _, _ -> [] end}
  def slice(_list), do: {:error, __MODULE__}

  def reduce(_list, {:halt, acc}, _fun), do: {:halted, acc}
  def reduce(list, {:suspend, acc}, fun), do: {:suspended, acc, &reduce(list, &1, fun)}
  def reduce([], {:cont, acc}, _fun), do: {:done, acc}
  def reduce([head | tail], {:cont, acc}, fun), do: reduce(tail, fun.(head, acc), fun)
end

defimpl Enumerable, for: Map do
  def count(map) do
    {:ok, map_size(map)}
  end

  def member?(map, {key, value}) do
    {:ok, match?(%{^key => ^value}, map)}
  end

  def member?(_map, _other) do
    {:ok, false}
  end

  def slice(map) do
    size = map_size(map)
    {:ok, size, &:maps.to_list/1}
  end

  def reduce(map, acc, fun) do
    Enumerable.List.reduce(:maps.to_list(map), acc, fun)
  end
end

defimpl Enumerable, for: Function do
  def count(_function), do: {:error, __MODULE__}
  def member?(_function, _value), do: {:error, __MODULE__}
  def slice(_function), do: {:error, __MODULE__}

  def reduce(function, acc, fun) when is_function(function, 2), do: function.(acc, fun)

  def reduce(function, _acc, _fun) do
    raise Protocol.UndefinedError,
      protocol: @protocol,
      value: function,
      description: "only anonymous functions of arity 2 are enumerable"
  end
end

defimpl Enumerable, for: Range do
  def reduce(first..last//step, acc, fun) do
    reduce(first, last, acc, fun, step)
  end

  # TODO: Remove me on v2.0
  reduce =
    quote generated: true do
      reduce(
        %{__struct__: Range, first: var!(first), last: var!(last)} = var!(range),
        var!(acc),
        var!(fun)
      )
    end

  def unquote(reduce) do
    step = if first <= last, do: 1, else: -1
    reduce(Map.put(range, :step, step), acc, fun)
  end

  defp reduce(_first, _last, {:halt, acc}, _fun, _step) do
    {:halted, acc}
  end

  defp reduce(first, last, {:suspend, acc}, fun, step) do
    {:suspended, acc, &reduce(first, last, &1, fun, step)}
  end

  defp reduce(first, last, {:cont, acc}, fun, step)
       when step > 0 and first <= last
       when step < 0 and first >= last do
    reduce(first + step, last, fun.(first, acc), fun, step)
  end

  defp reduce(_, _, {:cont, acc}, _fun, _up) do
    {:done, acc}
  end

  def member?(first..last//step, value) when is_integer(value) and step > 0 do
    {:ok, first <= value and value <= last and rem(value - first, step) == 0}
  end

  def member?(first..last//step, value) when is_integer(value) and step < 0 do
    {:ok, last <= value and value <= first and rem(value - first, step) == 0}
  end

  # TODO: Remove me on v2.0
  def member?(%{__struct__: Range, first: first, last: last} = range, value)
      when is_integer(value) do
    step = if first <= last, do: 1, else: -1
    member?(Map.put(range, :step, step), value)
  end

  def member?(_, _value) do
    {:ok, false}
  end

  def count(range) do
    {:ok, Range.size(range)}
  end

  def slice(first.._//step = range) do
    {:ok, Range.size(range), &slice(first + &1 * step, step * &3, &2)}
  end

  # TODO: Remove me on v2.0

  slice =
    quote generated: true do
      slice(%{__struct__: Range, first: var!(first), last: var!(last)} = var!(range))
    end

  def unquote(slice) do
    step = if first <= last, do: 1, else: -1
    slice(Map.put(range, :step, step))
  end

  defp slice(current, _step, 1), do: [current]

  defp slice(current, step, remaining) when remaining > 1 do
    [current | slice(current + step, step, remaining - 1)]
  end
end
