defmodule CPSolver.Utils.MutableOrder do
alias CPSolver.Utils.MutableArray
import CPSolver.Utils.MutableArray
@moduledoc """
'mutable order' structure.
Currently being used by AllDifferent.BC
The motivation:
every time the filtering happens, it needs to update some already sorted lists of lower and/or upper bounds.
Doing sorting from scratch is expensive, so the goal is to update and keep the array sorted
based only on the incoming changes.
`values` is a list of (unsorted) values - this would be a list of variables' lower or upper bounds
`sorted_index is a list, s.t. sorted_index[i] holds the position of values[i] in the sorted list.
The upshot is that `values` and `sorted_index` represent a sorted list of `values.
`updated_index` - the index of the changed value in `values`
`updated_value` - the new value for values[updated_index]
For instance:
values = [2, 8, 3, 5, 2]
The sorted index would be:
sorted_index = [0, 4, 2, 3, 1]
updated_index = 1, updated_value = 2
means that values[1] (that had value 8) had been updated to 2
Note: `value` and `sorted_index` are represented by :atomics
in order to facilitate fast access and updates in place
"""
@doc """
Creates an order structure from (unsorted) array
"""
def new(values) when is_list(values) do
n = length(values)
values_ref = MutableArray.new(values)
sort_index_ref = MutableArray.new(n)
value_positions_ref = MutableArray.new(n)
values
|> Enum.with_index()
|> Enum.sort()
|> Enum.reduce(0, fn {_val, idx}, pos_acc ->
array_update(sort_index_ref, pos_acc, idx)
array_update(value_positions_ref, idx, pos_acc)
pos_acc + 1
end)
%{values: values_ref, sort_index: sort_index_ref, positions: value_positions_ref}
end
@doc "Get value by index in sorted array"
def get(order_rec, index) do
array_get(order_rec.values, array_get(order_rec.sort_index, index))
end
def update(
%{values: values_ref, positions: positions_ref, sort_index: sort_index_ref} = _order_rec,
change
) do
update(values_ref, positions_ref, sort_index_ref, change)
end
def update(values, positions, sort_index, {change_index, new_value} = _change)
when is_reference(values) and is_reference(sort_index) and is_integer(change_index) and
is_integer(new_value) do
case array_get(values, change_index) do
current_value when current_value == new_value ->
:ok
current_value ->
change_pos = array_get(positions, change_index)
update_order_impl(
change_pos,
values,
positions,
sort_index,
new_value,
(current_value > new_value && 0) || array_size(values) - 1
)
array_update(values, change_index, new_value)
end
end
defp update_order_impl(current_pos, _values, _positions, _sort_index, _new_value, last_pos)
when current_pos == last_pos do
:ok
end
defp update_order_impl(pos, values, positions, sort_index, new_value, 0) do
next_pos = pos - 1
## The sort index is sorted by values indices refer to
## To find an actual value in referred lists (values, positions), we need to get an index value
next_pos_pointer = array_get(sort_index, next_pos)
if array_get(values, next_pos_pointer) > new_value do
swap(sort_index, pos, next_pos)
swap(positions, next_pos_pointer, array_get(sort_index, next_pos))
update_order_impl(next_pos, values, positions, sort_index, new_value, 0)
else
:ok
end
end
defp update_order_impl(pos, values, positions, sort_index, new_value, last_index) do
next_pos = pos + 1
## The sort index is sorted by values indices refer to
## To find an actual value in referred lists (values, positions), we need to get an index value
next_pos_pointer = array_get(sort_index, next_pos)
if array_get(values, next_pos_pointer) < new_value do
swap(sort_index, pos, next_pos)
swap(positions, next_pos_pointer, array_get(sort_index, next_pos))
update_order_impl(next_pos, values, positions, sort_index, new_value, last_index)
else
:ok
end
end
def to_sorted(%{values: values, sort_index: sort_index} = _order_rec, order \\ :asc) do
to_sorted(values, sort_index, order)
end
def to_sorted(values, sort_index, order) do
Enum.reduce(1..array_size(sort_index), [], fn idx, acc ->
sort_pos = array_get(sort_index, idx - 1)
[{sort_pos, array_get(values, sort_pos)} | acc]
end)
|> then(fn desc -> (order == :asc && Enum.reverse(desc)) || desc end)
end
def valid?(order_rec, order \\ :asc) do
ordered_values = to_sorted(order_rec, order) |> Enum.unzip() |> elem(1)
Enum.sort(ordered_values, order) == ordered_values
end
end