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