Packages
hunt_szymanski_diff
0.1.0
A port of the Hunt-Szymanski diff algorithm to Elixir, with a focus on performance and memory efficiency.
Current section
Files
Jump to
Current section
Files
lib/h_s_diff/optimize.ex
defmodule HSDiff.Optimize do
@doc """
Optimize the diff by:
- merging consecutive eq: blocks into eq: count
- merging consecutive del: blocks into del: count
- merging consecutive ins: blocks into a single ins: [lines...]
Example input:
[
eq: ["Line A"],
del: ["Line B"],
ins: ["Line B changed"],
eq: ["Line C", "Line D"],
del: ["Line E"],
ins: ["Line E new"],
eq: ["Line F"]
]
becomes:
[
eq: 1,
del: 1,
ins: ["Line B changed"],
eq: 2,
del: 1,
ins: ["Line E new"],
eq: 1
]
"""
def optimize(diff_blocks) do
# We'll accumulate optimized blocks in `acc` via do_optimize.
# Then reverse at the end so we return them in original order.
diff_blocks
|> do_optimize([])
|> Enum.reverse()
end
# -- Private recursive function that processes each block, merging with the last one if possible --
# 1) eq: lines
# If last block is eq: count, merge them by adding length(lines).
defp do_optimize([{:eq, lines} | rest], [{:eq, count} | acc_rest]) do
do_optimize(rest, [{:eq, count + length(lines)} | acc_rest])
end
# Otherwise, start a new eq: length(lines).
defp do_optimize([{:eq, lines} | rest], acc) do
do_optimize(rest, [{:eq, length(lines)} | acc])
end
# 2) del: lines
# If last block is del: count, merge by adding length(lines).
defp do_optimize([{:del, lines} | rest], [{:del, count} | acc_rest]) do
do_optimize(rest, [{:del, count + length(lines)} | acc_rest])
end
# Otherwise, start a new del: length(lines).
defp do_optimize([{:del, lines} | rest], acc) do
do_optimize(rest, [{:del, length(lines)} | acc])
end
# 3) ins: lines
# If last block is ins: existing, merge by concatenating line lists.
defp do_optimize([{:ins, new_lines} | rest], [{:ins, existing_lines} | acc_rest]) do
do_optimize(rest, [{:ins, existing_lines ++ new_lines} | acc_rest])
end
# Otherwise, start a new ins: new_lines.
defp do_optimize([{:ins, new_lines} | rest], acc) do
do_optimize(rest, [{:ins, new_lines} | acc])
end
# If it's some other block type (in normal usage, we only have eq/del/ins),
# just keep it as is.
defp do_optimize([block | rest], acc) do
do_optimize(rest, [block | acc])
end
# No more blocks => return the accumulator.
defp do_optimize([], acc), do: acc
end