lib/r_list/ruby.ex

defmodule RList.Ruby do
  @moduledoc """
  Summarized all of Ruby's Array functions.
  Functions corresponding to the following patterns are not implemented
   - When a function with the same name already exists in Elixir.
   - When a method name includes `!`.
   - &, *, +, -, <<, <=>, ==, [], []=.
  """
  @spec __using__(any) :: list
  defmacro __using__(_opts) do
    RUtils.define_all_functions!(__MODULE__)
  end

  @type type_pattern :: number() | String.t() | Range.t() | Regex.t()
  @type type_enumerable :: Enumerable.t()

  import REnum.Support

  # https://ruby-doc.org/core-3.1.0/Array.html
  # [:all?, :any?, :append, :assoc, :at, :bsearch, :bsearch_index, :clear, :collect, :collect!, :combination, :compact, :compact!, :concat, :count, :cycle, :deconstruct, :delete, :delete_at, :delete_if, :difference, :dig, :drop, :drop_while, :each, :each_index, :empty?, :eql?, :fetch, :fill, :filter, :filter!, :find_index, :first, :flatten, :flatten!, :hash, :include?, :index, :initialize_copy, :insert, :inspect, :intersect?, :intersection, :join, :keep_if, :last, :length, :map, :map!, :max, :min, :minmax, :none?, :old_to_s, :one?, :pack, :permutation, :pop, :prepend, :product, :push, :rassoc, :reject, :reject!, :repeated_combination, :repeated_permutation, :replace, :reverse, :reverse!, :reverse_each, :rindex, :rotate, :rotate!, :sample, :select, :select!, :shift, :shuffle, :shuffle!, :size, :slice, :slice!, :sort, :sort!, :sort_by!, :sum, :take, :take_while, :to_a, :to_ary, :to_h, :to_s, :transpose, :union, :uniq, :uniq!, :unshift, :values_at, :zip]
  # |> RUtils.required_functions([List, REnum])
  # ✔ append
  # ✔ assoc
  # × bsearch
  # × bsearch_index
  # ✔ clear
  # ✔ combination
  # × deconstruct
  # ✔ delete_if
  # ✔ difference
  # ✔ dig
  # ✔ each_index
  # ✔ eql?
  # ✔ fill
  # hash TODO: Low priority
  # ✔ index
  # × initialize_copy
  # ✔ insert
  # ✔ inspect
  # ✔ intersect?
  # ✔ intersection
  # ✔ keep_if
  # ✔ length
  # × old_to_s
  # pack TODO: Low priority
  # ✔ permutation
  # ✔ pop
  # ✔ prepend
  # ✔ push
  # ✔ rassoc
  # ✔ repeated_combination
  # ✔ repeated_permutation
  # × replace
  # ✔ rindex
  # ✔ rotate
  # ✔ sample
  # ✔ shift
  # ✔ size
  # ✔ to_ary
  # ✔ to_s
  # ✔ transpose
  # ✔ union
  # ✔ unshift
  # ✔ values_at

  @doc """
  Appends trailing elements.

  ## Examples
      iex> [:foo, 'bar', 2]
      iex> |> RList.push([:baz, :bat])
      [:foo, 'bar', 2, :baz, :bat]

      iex> [:foo, 'bar', 2]
      iex> |> RList.push(:baz)
      [:foo, 'bar', 2, :baz]
  """
  @spec push(list(), list() | any) :: list()
  def push(list, elements_or_element) do
    list ++ List.wrap(elements_or_element)
  end

  @doc """
  Returns [].

  ## Examples
      iex> [[:foo, 0], [2, 4], [4, 5, 6], [4, 5]]
      iex> |> RList.clear()
      []
  """
  @spec clear(list()) :: []
  def clear(list) when is_list(list), do: []

  @doc """
  Returns Stream that is each repeated combinations of elements of given list. The order of combinations is indeterminate.
  ## Examples
      iex> RList.combination([1, 2, 3, 4], 1)
      iex> |> Enum.to_list()
      [[1],[2],[3],[4]]

      iex> RList.combination([1, 2, 3, 4], 3)
      iex> |> Enum.to_list()
      [[1,2,3],[1,2,4],[1,3,4],[2,3,4]]

      iex> RList.combination([1, 2, 3, 4], 0)
      iex> |> Enum.to_list()
      [[]]

      iex> RList.combination([1, 2, 3, 4], 5)
      iex> |> Enum.to_list()
      []
  """
  @spec combination(list(), non_neg_integer()) :: type_enumerable
  def combination(list, length) do
    _combination(list, length) |> REnum.lazy()
  end

  @doc """
  Calls the function with combinations of elements of given list; returns :ok. The order of combinations is indeterminate.
  ## Examples
      iex> RList.combination([1, 2, 3, 4], 1, &(IO.inspect(&1)))
      # [1]
      # [2]
      # [3]
      # [4]
      :ok

      iex> RList.combination([1, 2, 3, 4], 3, &(IO.inspect(&1)))
      # [1, 2, 3]
      # [1, 2, 4]
      # [1, 3, 4]
      # [2, 3, 4]
      :ok

      iex> RList.combination([1, 2, 3, 4], 0, &(IO.inspect(&1)))
      # []
      :ok

      iex> RList.combination([1, 2, 3, 4], 5, &(IO.inspect(&1)))
      :ok
  """
  @spec combination(list(), non_neg_integer, function()) :: :ok
  def combination(list, n, func) do
    combination(list, n) |> Enum.each(fn el -> func.(el) end)
  end

  def _combination(_elements, 0), do: [[]]

  def _combination([], _), do: []

  def _combination([head | tail], n) do
    for(comb <- _combination(tail, n - 1), do: [head | comb]) ++ _combination(tail, n)
  end

  @doc """
  Returns Stream that is each repeated combinations of elements of given list. The order of combinations is indeterminate.

  ## Examples
      iex> RList.repeated_combination([1, 2, 3], 1)
      iex> |> Enum.to_list()
      [[1], [2], [3]]

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

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

      iex> RList.repeated_combination([1, 2, 3], 0)
      iex> |> Enum.to_list()
      [[]]

      iex> RList.repeated_combination([1, 2, 3], 5)
      iex> |> Enum.to_list()
      [
        [1, 1, 1, 1, 1],
        [1, 1, 1, 1, 2],
        [1, 1, 1, 1, 3],
        [1, 1, 1, 2, 2],
        [1, 1, 1, 2, 3],
        [1, 1, 1, 3, 3],
        [1, 1, 2, 2, 2],
        [1, 1, 2, 2, 3],
        [1, 1, 2, 3, 3],
        [1, 1, 3, 3, 3],
        [1, 2, 2, 2, 2],
        [1, 2, 2, 2, 3],
        [1, 2, 2, 3, 3],
        [1, 2, 3, 3, 3],
        [1, 3, 3, 3, 3],
        [2, 2, 2, 2, 2],
        [2, 2, 2, 2, 3],
        [2, 2, 2, 3, 3],
        [2, 2, 3, 3, 3],
        [2, 3, 3, 3, 3],
        [3, 3, 3, 3, 3]
      ]
  """
  @spec repeated_combination(list(), non_neg_integer()) :: type_enumerable
  def repeated_combination(list, length) do
    _repeated_combination(list, length) |> REnum.lazy()
  end

  @doc """
  Calls the function with each repeated combinations of elements of given list; returns :ok. The order of combinations is indeterminate.
  ## Examples
      iex> RList.repeated_combination([1, 2, 3, 4], 2, &(IO.inspect(&1)))
      # [1, 1]
      # [1, 2]
      # [1, 3]
      # [2, 2]
      # [2, 3]
      # [3, 3]
      :ok
  """
  @spec repeated_combination(list(), non_neg_integer, function()) :: :ok
  def repeated_combination(list, n, func) do
    _repeated_combination(list, n) |> Enum.each(fn el -> func.(el) end)
  end

  def _repeated_combination(_elements, 0), do: [[]]

  def _repeated_combination([], _), do: []

  def _repeated_combination([h | t] = s, n) do
    for(l <- _repeated_combination(s, n - 1), do: [h | l]) ++ _repeated_combination(t, n)
  end

  @doc """
  Returns Stream that is each repeated permutations of elements of given list. The order of permutations is indeterminate.

  ## Examples
      iex> RList.permutation([1, 2, 3], 1)
      iex> |> Enum.to_list()
      [[1],[2],[3]]

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

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

      iex> RList.permutation([1, 2, 3], 0)
      iex> |> Enum.to_list()
      [[]]

      iex> RList.permutation([1, 2, 3], 4)
      iex> |> Enum.to_list()
      []
  """
  @spec permutation(list(), non_neg_integer() | nil) :: type_enumerable

  def permutation(list, length \\ nil) do
    n = length || Enum.count(list)

    cond do
      n <= 0 ->
        [[]]

      n == Enum.count(list) ->
        _permutation(list)

      n >= Enum.count(list) ->
        []

      true ->
        _permutation(list)
        |> Enum.map(&Enum.take(&1, n))
        |> Enum.uniq()
    end
    |> REnum.lazy()
  end

  defp _permutation(list),
    do: for(elem <- list, rest <- permutation(list -- [elem]), do: [elem | rest])

  @doc """
  Returns Stream that is each repeated permutations of elements of given list. The order of permutations is indeterminate.

  ## Examples
      iex> RList.repeated_permutation([1, 2], 1)
      iex> |> Enum.to_list()
      [[1],[2]]

      iex> RList.repeated_permutation([1, 2], 2)
      iex> |> Enum.to_list()
      [[1,1],[1,2],[2,1],[2,2]]

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

      iex> RList.repeated_permutation([1, 2], 0)
      iex> |> Enum.to_list()
      [[]]
  """
  @spec repeated_permutation(list(), non_neg_integer()) :: type_enumerable

  def repeated_permutation(list, length) do
    _repeated_permutation(list, length) |> REnum.lazy()
  end

  defp _repeated_permutation([], _), do: [[]]
  defp _repeated_permutation(_, 0), do: [[]]

  defp _repeated_permutation(list, length),
    do: for(x <- list, y <- _repeated_permutation(list, length - 1), do: [x | y])

  @doc """
  Returns differences between list1 and list2.

  ## Examples
      iex> [0, 1, 1, 2, 1, 1, 3, 1, 1]
      iex> |> RList.difference([1])
      [0, 2, 3]

      iex> [0, 1, 2]
      iex> |> RList.difference([4])
      [0, 1, 2]
  """
  @spec difference(list(), list()) :: list()
  def difference(list1, list2) do
    list1
    |> Enum.reject(fn el ->
      el in list2
    end)
  end

  @doc """
  Finds and returns the element in nested elements that is specified by index and identifiers.

  ## Examples
      iex> [:foo, [:bar, :baz, [:bat, :bam]]]
      iex> |> RList.dig(1)
      [:bar, :baz, [:bat, :bam]]

      iex> [:foo, [:bar, :baz, [:bat, :bam]]]
      iex> |> RList.dig(1, [2])
      [:bat, :bam]

      iex> [:foo, [:bar, :baz, [:bat, :bam]]]
      iex> |> RList.dig(1, [2, 0])
      :bat

      iex> [:foo, [:bar, :baz, [:bat, :bam]]]
      iex> |> RList.dig(1, [2, 3])
      nil
  """
  @spec dig(list(), integer, list()) :: any
  def dig(list, index, identifiers \\ []) do
    el = Enum.at(list, index)

    if(Enum.any?(identifiers)) do
      [next_index | next_identifiers] = identifiers

      dig(el, next_index, next_identifiers)
    else
      el
    end
  end

  @doc """
  Returns the index of a specified element.

  ## Examples
      iex> [:foo, "bar", 2, "bar"]
      iex> |> RList.index("bar")
      1

      iex> [2, 4, 6, 8]
      iex> |> RList.index(5..7)
      2

      iex> [2, 4, 6, 8]
      iex> |> RList.index(&(&1 == 8))
      3
  """
  @spec index(list(), type_pattern | function()) :: any
  def index(list, func_or_pattern) when is_function(func_or_pattern) do
    Enum.find_index(list, func_or_pattern)
  end

  def index(list, func_or_pattern) do
    index(list, match_function(func_or_pattern))
  end

  @doc """
  Returns true if list1 == list2.

  ## Examples
      iex>  [:foo, 'bar', 2]
      iex> |> RList.eql?([:foo, 'bar', 2])
      true

      iex>  [:foo, 'bar', 2]
      iex> |> RList.eql?([:foo, 'bar', 3])
      false
  """
  @spec eql?(list(), list()) :: boolean()
  def eql?(list1, list2) do
    list1 == list2
  end

  @doc """
  Returns true if the list1 and list2 have at least one element in common, otherwise returns false.

  ## Examples
      iex> [1, 2, 3]
      iex> |> RList.intersect?([3, 4, 5])
      true

      iex> [1, 2, 3]
      iex> |> RList.intersect?([5, 6, 7])
      false
  """
  @spec intersect?(list(), list()) :: boolean()
  def intersect?(list1, list2) do
    intersection(list1, list2)
    |> Enum.count() > 0
  end

  @doc """
  Returns a new list containing each element found both in list1 and in all of the given list2; duplicates are omitted.

  ## Examples
      iex> [1, 2, 3]
      iex> |> RList.intersection([3, 4, 5])
      [3]

      iex> [1, 2, 3]
      iex> |> RList.intersection([5, 6, 7])
      []

      iex> [1, 2, 3]
      iex> |> RList.intersection([1, 2, 3])
      [1, 2, 3]
  """
  @spec intersection(list(), list()) :: list()
  def intersection(list1, list2) do
    m1 = MapSet.new(list1)
    m2 = MapSet.new(list2)

    MapSet.intersection(m1, m2)
    |> Enum.to_list()
  end

  @doc """
  Returns one or more random elements.
  """
  def sample(list, n \\ 1) do
    taked =
      list
      |> Enum.shuffle()
      |> Enum.take(n)

    if(taked |> Enum.count() > 1) do
      taked
    else
      [head | _] = taked
      head
    end
  end

  if(VersionManager.support_version?()) do
    @doc """
    Fills the list with the provided value. The filler can be either a function or a fixed value.

    ## Examples
        iex> RList.fill(~w[a b c d], "x")
        ["x", "x", "x", "x"]

        iex> RList.fill(~w[a b c d], "x", 0..1)
        ["x", "x", "c", "d"]

        iex> RList.fill(~w[a b c d], fn _, i -> i * i end)
        [0, 1, 4, 9]

        iex> RList.fill(~w[a b c d], fn _, i -> i * 2 end, 0..1)
        [0, 2, "c", "d"]
    """
  end

  @spec fill(list(), any) :: list()
  def fill(list, filler_fun) when is_function(filler_fun) do
    Enum.with_index(list, filler_fun)
  end

  def fill(list, filler), do: Enum.map(list, fn _ -> filler end)

  @spec fill(list(), any, Range.t()) :: list()
  def fill(list, filler_fun, a..b) when is_function(filler_fun) do
    Enum.with_index(list, fn
      x, i when i >= a and i <= b -> filler_fun.(x, i)
      x, _i -> x
    end)
  end

  def fill(list, filler, fill_range), do: fill(list, fn _, _ -> filler end, fill_range)

  @doc """
  Returns a list containing the elements in list corresponding to the given selector(s).
  The selectors may be either integer indices or ranges.

  ## Examples

      iex> RList.values_at(~w[a b c d e f], [1, 3, 5])
      ["b", "d", "f"]

      iex> RList.values_at(~w[a b c d e f], [1, 3, 5, 7])
      ["b", "d", "f", nil]

      iex> RList.values_at(~w[a b c d e f], [-1, -2, -2, -7])
      ["f", "e", "e", nil]

      iex> RList.values_at(~w[a b c d e f], [4..6, 3..5])
      ["e", "f", nil, "d", "e", "f"]

      iex> RList.values_at(~w[a b c d e f], 4..6)
      ["e", "f", nil]
  """
  @spec values_at(list(), [integer | Range.t()] | Range.t()) :: list()
  def values_at(list, indices) do
    indices
    |> Enum.map(fn
      i when is_integer(i) -> i
      i -> Enum.to_list(i)
    end)
    |> List.flatten()
    |> Enum.map(&Enum.at(list, &1))
  end

  @doc """
  Returns a new list by joining two lists, excluding any duplicates and preserving the order from the given lists.
  ## Examples
      iex> RList.union(["a", "b", "c"], [ "c", "d", "a"])
      ["a", "b", "c", "d"]

      iex> ["a"] |> RList.union(["e", "b"]) |> RList.union(["a", "c", "b"])
      ["a", "e", "b", "c"]
  """
  @spec union(list(), list()) :: list()
  def union(list_a, list_b), do: Enum.uniq(list_a ++ list_b)

  @doc """
  Prepends elements to the front of the list, moving other elements upwards.
  ## Examples
      iex> RList.unshift(~w[b c d], "a")
      ["a", "b", "c", "d"]

      iex> RList.unshift(~w[b c d], [1, 2])
      [1, 2, "b", "c", "d"]
  """
  @spec unshift(list(), any) :: list()
  def unshift(list, prepend) when is_list(prepend), do: prepend ++ list
  def unshift(list, prepend), do: [prepend | list]

  @doc """
  Splits the list into the first n elements and the rest. Returns nil if the list is empty.
  ## Examples
      iex> RList.shift([])
      nil

      iex> RList.shift(~w[-m -q -filename])
      {["-m"], ["-q", "-filename"]}

      iex> RList.shift(~w[-m -q -filename], 2)
      {["-m", "-q"], ["-filename"]}
  """
  @spec shift(list(), integer) :: {list(), list()} | nil
  def shift(list, count \\ 1)
  def shift([], _count), do: nil
  def shift(list, count), do: Enum.split(list, count)

  @doc """
  Splits the list into the last n elements and the rest. Returns nil if the list is empty.
  ## Examples
      iex> RList.pop([])
      nil

      iex> RList.pop(~w[-m -q -filename test.txt])
      {["test.txt"], ["-m", "-q", "-filename"]}

      iex> RList.pop(~w[-m -q -filename test.txt], 2)
      {["-filename", "test.txt"], ["-m", "-q"]}
  """
  @spec pop(list(), integer) :: {list(), list()} | nil
  def pop(list, count \\ 1) do
    list
    |> Enum.reverse()
    |> shift(count)
    |> _pop()
  end

  defp _pop(nil), do: nil

  defp _pop(tuple) do
    {
      elem(tuple, 0) |> Enum.reverse(),
      elem(tuple, 1) |> Enum.reverse()
    }
  end

  @doc """
  Returns the first element that is a List whose last element `==` the specified term.

  ## Examples

      iex> [{:foo, 0}, [2, 4], [4, 5, 6], [4, 5]]
      iex> |> RList.rassoc(4)
      [2, 4]

      iex> [{:foo, 0}, [2, 4], [4, 5, 6], [4, 5]]
      iex> |> RList.rassoc(0)
      {:foo, 0}

      iex> [[1, "one"], [2, "two"], [3, "three"], ["ii", "two"]]
      iex> |> RList.rassoc("two")
      [2, "two"]

      iex> [[1, "one"], [2, "two"], [3, "three"], ["ii", "two"]]
      iex> |> RList.rassoc("four")
      nil

      iex> [] |> RList.rassoc(4)
      nil

      iex> [[]] |> RList.rassoc(4)
      nil

      iex> [{}] |> RList.rassoc(4)
      nil
  """
  @spec rassoc([list | tuple], any) :: list | nil
  def rassoc(list, key) do
    Enum.find(list, fn
      nil -> nil
      [] -> nil
      {} -> nil
      x when is_tuple(x) -> x |> Tuple.to_list() |> Enum.reverse() |> hd() == key
      x -> x |> Enum.reverse() |> hd() == key
    end)
  end

  @doc """
  Returns the first element in list that is an List whose first key == obj:

  ## Examples
      iex> [{:foo, 0}, [2, 4], [4, 5, 6], [4, 5]]
      iex> |> RList.assoc(4)
      [4, 5, 6]

      iex> [[:foo, 0], [2, 4], [4, 5, 6], [4, 5]]
      iex> |> RList.assoc(1)
      nil

      iex> [[:foo, 0], [2, 4], %{a: 4, b: 5, c: 6}, [4, 5]]
      iex> |> RList.assoc({:a, 4})
      %{a: 4, b: 5, c: 6}
  """
  @spec assoc(list(), any) :: any
  def assoc(list, key) do
    list
    |> Enum.find(fn
      nil -> nil
      [] -> nil
      {} -> nil
      x when is_tuple(x) -> x |> Tuple.to_list() |> hd() == key
      x -> x |> Enum.to_list() |> hd() == key
    end)
  end

  @doc """
  Returns the index of the last element found in in the list. Returns nil if no match is found.
  ## Examples
      iex> RList.rindex(~w[a b b b c], "b")
      3

      iex> RList.rindex(~w[a b b b c], "z")
      nil

      iex> RList.rindex(~w[a b b b c], fn x -> x == "b" end)
      3
  """
  @spec rindex(list(), any) :: integer | nil
  def rindex(list, finder) when is_function(finder) do
    list
    |> Enum.with_index()
    |> Enum.reverse()
    |> Enum.find_value(fn {x, i} -> finder.(x) && i end)
  end

  def rindex(list, finder), do: rindex(list, &Kernel.==(&1, finder))

  @doc """
  Rotate the list so that the element at count is the first element of the list.

  ## Examples
      iex> RList.rotate(~w[a b c d])
      ["b", "c", "d", "a"]

      iex> RList.rotate(~w[a b c d], 2)
      ["c", "d", "a", "b"]

      iex> RList.rotate(~w[a b c d], -3)
      ["b", "c", "d", "a"]
  """
  @spec rotate(list(), integer) :: list()
  def rotate(list, count \\ 1) do
    {first, last} = Enum.split(list, count)
    last ++ first
  end

  @doc """
  Returns list.

  ## Examples
      iex> RList.to_ary(["c", "d", "a", "b"])
      ["c", "d", "a", "b"]
  """
  @spec to_ary(list()) :: list()
  def to_ary(list), do: list

  defdelegate append(list, elements), to: __MODULE__, as: :push
  defdelegate delete_if(list, func), to: Enum, as: :reject
  defdelegate keep_if(list, func), to: Enum, as: :filter
  defdelegate length(list), to: Enum, as: :count
  defdelegate size(list), to: Enum, as: :count
  defdelegate to_s(list), to: Kernel, as: :inspect
  defdelegate inspect(list), to: Kernel, as: :inspect
  defdelegate each_index(list, func), to: Enum, as: :with_index
  defdelegate insert(list, index, element), to: List, as: :insert_at
  defdelegate transpose(list_of_lists), to: List, as: :zip
  defdelegate prepend(list, count \\ 1), to: __MODULE__, as: :shift
  defdelegate all_combination(list, length), to: __MODULE__, as: :repeated_combination
  defdelegate all_combination(list, length, func), to: __MODULE__, as: :repeated_combination
end