Current section
Files
Jump to
Current section
Files
lib/utils/combinators.ex
defmodule Formulae.Combinators do
@moduledoc """
Helper to calculate all the combinations of the list given.
_Examples_
iex> Formulae.Combinators.combinations(~w|a b c d|a, 2)
[[:a, :b], [:a, :c], [:a, :d], [:b, :c], [:b, :d], [:c, :d]]
iex> Formulae.Combinators.permutations(~w|a b c d|a, 2)
[[:a, :b], [:a, :c], [:a, :d], [:b, :a], [:b, :c], [:b, :d],
[:c, :a], [:c, :b], [:c, :d], [:d, :a], [:d, :b], [:d, :c]]
iex> Formulae.Combinators.repeated_permutations(~w|a b c d|a, 2)
[[:a, :a], [:a, :b], [:a, :c], [:a, :d], [:b, :a], [:b, :b],
[:b, :c], [:b, :d], [:c, :a], [:c, :b], [:c, :c], [:c, :d],
[:d, :a], [:d, :b], [:d, :c], [:d, :d]]
"""
@max_permutations Application.get_env(:formulae, :max_permutations, 20)
Enum.each(2..@max_permutations, fn n ->
@n n
# combination
# for {i1, i1_idx} <- Enum.with_index(l),
# {i2, i2_idx} <- Enum.with_index(l),
# {i3, i3_idx} <- Enum.with_index(l),
# i2_idx > i1_idx,
# i3_idx > i2_idx,
# do: [i1, i2, i3]
defmacrop unquote(:"combinations_#{@n}")(l) do
var = fn i -> {:"i#{i}", [], Elixir} end
idx = fn i -> {:"i#{i}_idx", [], Elixir} end
clause = fn i ->
{:<-, [],
[
{var.(i), idx.(i)},
{{:., [], [{:__aliases__, [alias: false], [:Enum]}, :with_index]}, [], [l]}
]}
end
guard = fn i -> {:>, [context: Elixir, import: Kernel], [idx.(i), idx.(i - 1)]} end
{:for, [],
Enum.reverse([
[do: Enum.map(1..@n, var)] | Enum.map(@n..2, guard) ++ Enum.map(@n..1, clause)
])}
end
# # permutation
# for {i1, i1_idx} <- Enum.with_index(l),
# {i2, i2_idx} <- Enum.with_index(l),
# {i3, i3_idx} <- Enum.with_index(l),
# i1_idx != i2_idx,
# i1_idx != i3_idx,
# i2_idx != i3_idx,
# do: [i1, i2, i3]
defmacrop unquote(:"permutations_#{@n}")(l) do
var = fn i -> {:"i#{i}", [], nil} end
idx = fn i -> {:"i#{i}_idx", [], Elixir} end
clause = fn i ->
{:<-, [],
[
{var.(i), idx.(i)},
{{:., [], [{:__aliases__, [alias: false], [:Enum]}, :with_index]}, [], [l]}
]}
end
guards =
@n..1
|> Enum.map(idx)
|> combinations_2()
|> Enum.map(&{:!=, [context: Elixir, import: Kernel], &1})
{:for, [],
Enum.reverse([
[do: Enum.map(1..@n, var)] | guards ++ Enum.map(@n..1, clause)
])}
end
# # repeated permutation
# for i1 <- l,
# i2 <- l,
# i3 <- l,
# do: [i1, i2, i3]
defmacrop unquote(:"repeated_permutations_#{@n}")(l) do
var = fn i -> {:"i#{i}", [], nil} end
clause = fn i -> {:<-, [], [var.(i), l]} end
{:for, [], Enum.reverse([[do: Enum.map(1..@n, var)] | Enum.map(@n..1, clause)])}
end
end)
@doc "Calculates all combinations of the list, given as the first parameter"
@spec combinations(list :: list(), count :: non_neg_integer()) :: [list()]
def combinations([], _), do: []
def combinations(list, n) when length(list) < n, do: []
def combinations(list, n) when length(list) == n, do: [list]
def combinations(list, 1), do: Enum.map(list, &[&1])
Enum.each(2..@max_permutations, fn n ->
@n n
def combinations(list, unquote(@n)),
do: Enum.map(unquote(:"combinations_#{@n}")(list), & &1)
end)
@doc "Calculates all permutations of the list, given as the first parameter"
@spec permutations(list :: list(), count :: non_neg_integer()) :: [list()]
def permutations([], _), do: []
def permutations(list, n) when length(list) < n, do: []
def permutations(list, 1), do: Enum.map(list, &[&1])
Enum.each(2..@max_permutations, fn n ->
@n n
def permutations(list, unquote(@n)),
do: Enum.map(unquote(:"permutations_#{@n}")(list), & &1)
end)
@doc "Calculates all repeated permutations of the list, given as the first parameter"
@spec repeated_permutations(list :: list(), count :: non_neg_integer()) :: [list()]
def repeated_permutations([], _), do: []
def repeated_permutations(list, 1), do: Enum.map(list, &[&1])
Enum.each(2..@max_permutations, fn n ->
@n n
def repeated_permutations(list, unquote(@n)),
do: Enum.map(unquote(:"repeated_permutations_#{@n}")(list), & &1)
end)
end