Current section
Files
Jump to
Current section
Files
lib/tablex/optimizer/helper.ex
defmodule Tablex.Optimizer.Helper do
@type order() :: :h2l | :l2h
@type rule :: Tablex.Table.rule()
@doc """
Order a list of table rules by priority, high to low.
"""
def order_by_priority_high_to_lower(rules, hp),
do: order_by_priority(rules, :h2l, hp)
@doc """
Order an already sorted, list of rules by hit policy.
"""
@spec order_by_priority([rule()], current_order :: order(), Tablex.HitPolicy.hit_policy()) ::
[rule()]
def order_by_priority(rules, :h2l, :reverse_merge),
do: rules |> Enum.sort_by(fn_reverse_orders())
def order_by_priority(rules, :h2l, _),
do: rules |> Enum.sort_by(fn_keep_orders())
def order_by_priority(rules, :l2h, :reverse_merge),
do: rules |> Enum.sort_by(fn_keep_orders())
def order_by_priority(rules, :l2h, _),
do: rules |> Enum.sort_by(fn_reverse_orders())
defp fn_keep_orders, do: fn [n | _] -> n end
defp fn_reverse_orders, do: fn [n | _] -> -n end
@doc """
Sort the rules according to a hit policy.
"""
def sort_rules(rules, hit_policy) do
sorting =
case hit_policy do
:reverse_merge -> :l2h
_ -> :h2l
end
order_by_priority(rules, sorting, hit_policy)
end
@doc """
Fix ids of rules.
"""
@spec fix_ids([rule()]) :: [rule()]
def fix_ids(rules) do
Stream.with_index(rules, 1)
|> Enum.map(fn {[_ | tail], id} -> [id | tail] end)
end
@doc """
Check if a input condition covers by another.
"""
@spec cover_input?(covering :: any(), target :: any()) :: boolean()
def cover_input?(input, input) do
true
end
def cover_input?(existing_input, input) do
Stream.zip(existing_input, input)
|> Enum.all?(fn {existing, new} -> stub_covers?(existing, new) end)
end
def stub_covers?(:any, _), do: true
def stub_covers?(same, same), do: true
def stub_covers?({:>, n}, {cmp, m})
when is_number(n) and is_number(m) and n < m and cmp in [:>, :>=],
do: true
def stub_covers?({:>=, n}, {cmp, m})
when is_number(n) and is_number(m) and n <= m and cmp in [:>=, :>],
do: true
def stub_covers?({:<, n}, {cmp, m})
when is_number(n) and is_number(m) and n > m and cmp in [:<, :<=],
do: true
def stub_covers?({:<=, n}, {cmp, m})
when is_number(n) and is_number(m) and n >= m and cmp in [:<, :<=],
do: true
def stub_covers?(expr, list) when is_list(list),
do: Enum.all?(list, &stub_covers?(expr, &1))
def stub_covers?(list, item) when is_list(list),
do: Enum.any?(list, &stub_covers?(&1, item))
def stub_covers?(_, _),
do: false
def cover_output?(output, output) do
true
end
def cover_output?(high_output, low_output) do
Stream.zip(high_output, low_output)
|> Enum.all?(fn
{:any, :any} ->
true
{:any, _} ->
false
_ ->
true
end)
end
@doc """
Merge two outputs. Stub value with higher priority wins.
## Example
iex > merge_outputs([1, 2, 3], [2, 4, 6])
[1, 2, 3]
iex > merge_outputs([:any, 2, :any], [2, :any, 6])
[2, 2, 6]
"""
def merge_outputs(high_output, low_output) do
Stream.zip(high_output, low_output)
|> Enum.map(fn
{:any, stub} ->
stub
{stub, _} ->
stub
end)
end
@doc """
Check if an output is meaningful. A meaningful output is one that
does not contain all :any elements.
"""
def meaningful_output?(output) do
output |> Enum.any?(&(&1 != :any))
end
def exclusive?([_, {:input, input1} | _], [_, {:input, input2} | _]) do
exclusive?(input1, input2)
end
def exclusive?(input1, input2) do
Stream.zip(input1, input2)
|> Enum.any?(fn
# `a` and `b` are both specified (not `:any`) and they are
# different, which means these two rules will never be both
# hit by the same context.
{a, b} when a != b and a != :any and b != :any ->
true
_ ->
false
end)
end
def input_mergeable?(input1, input2) do
cover_input?(input1, input2) or cover_input?(input2, input1) or
only_one_different_stub?(input1, input2)
end
defp only_one_different_stub?(input1, input2) do
diff =
Stream.zip(input1, input2)
|> Stream.reject(fn
{same, same} -> true
_ -> false
end)
|> Enum.count()
diff == 1
end
@doc """
Merge two inputs.
"""
def merge_inputs(input1, input2) do
Stream.zip(input1, input2)
|> Enum.map(fn {a, b} -> merge_input_stubs(a, b) end)
end
@doc """
Merge two input stubs
"""
def merge_input_stubs(expr, expr), do: expr
def merge_input_stubs(expr1, expr2) do
cond do
stub_covers?(expr1, expr2) ->
expr1
stub_covers?(expr2, expr1) ->
expr2
true ->
List.flatten([expr1, expr2]) |> Enum.uniq() |> Enum.sort()
end
end
end