Files
Odin/core/diff/histogram.odin

192 lines
5.2 KiB
Odin

#+private
package diff
/*
Histogram diff. Each region is split around a run of equal tokens anchored on its rarest tokens. The regions
before and after the run are then diffed the same way. How rare a token is counts only the region of `a`.
A token occurring more than `HISTOGRAM_MAX_OCCURRENCES` times in the region cannot anchor a run. A region whose
tokens in common are all that frequent is diffed again by `rediff`: Myers after `.Prune`'s preparation.
*/
// A token occurring more often than this in the region of `a` is too frequent to anchor on.
HISTOGRAM_MAX_OCCURRENCES :: 64
// The end of a chain of occurrences.
HISTOGRAM_NONE :: -1
Histogram :: struct {
a, b: []i32,
count: []i32, // the occurrences of each ID in the region of `a`, zero outside a search
first: []i32, // the first occurrence of each ID in the region of `a`, wherever `count` is not 0
next: []i32, // the next occurrence of `a[i]` in the region, or `HISTOGRAM_NONE`
}
// What the search of a region found.
Histogram_Found :: enum u8 {
Nothing, // no tokens in common
Run, // a run to split around
Fall_Back, // only tokens too frequent to anchor on
}
@(require_results)
histogram_scratch_size :: proc "contextless" (n, m: int, id_count: int) -> int {
return 2*scratch_bytes(i32, id_count) + scratch_bytes(i32, n) + rediff_scratch_size(n, m, id_count)
}
// Diffs `a` against `b` into zeroed `changes`. Gives back its scratch.
histogram :: proc(a, b: []i32, id_count: int, changes: Changes, s: ^Scratch, max_cost: int, loc := #caller_location) {
start := s.pos
h := Histogram{
a = a,
b = b,
count = scratch_take(s, i32, id_count, loc),
first = scratch_take(s, i32, id_count, loc),
next = scratch_take(s, i32, len(a), loc),
}
for &x in h.count {
x = 0
}
stack: [MYERS_MAX_DEPTH]Box
depth := 0
box := Box{0, len(a), 0, len(b), false}
for {
switch {
case box.a_lo == box.a_hi:
_mark_inserted(changes, box.b_lo, box.b_hi)
case box.b_lo == box.b_hi:
_mark_deleted(changes, box.a_lo, box.a_hi)
case:
run, found := histogram_best_run(&h, box)
switch found {
case .Fall_Back:
this := Compact_Side{a, 0, nil}
other := Compact_Side{b, _inserted_offset(changes), nil}
rediff(changes, this, other, {box.a_lo, box.a_hi}, {box.b_lo, box.b_hi}, id_count, max_cost, s, loc)
case .Nothing:
_mark_deleted(changes, box.a_lo, box.a_hi)
_mark_inserted(changes, box.b_lo, box.b_hi)
case .Run:
lo := Box{box.a_lo, run.a_lo, box.b_lo, run.b_lo, false}
hi := Box{run.a_hi, box.a_hi, run.b_hi, box.b_hi, false}
// The smaller side first, keeping the stack within `MYERS_MAX_DEPTH`
if box_size(lo) <= box_size(hi) {
stack[depth] = hi
box = lo
} else {
stack[depth] = lo
box = hi
}
depth += 1
continue
}
}
if depth == 0 {
break
}
depth -= 1
box = stack[depth]
}
s.pos = start
}
/*
Finds the run of equal tokens to split `box` around. Both sides are non-empty.
Each token of `b`, in order, anchors a run at every occurrence of its ID in `a` when it is rare enough. The
run grows both ways. It is the best if it is longer than the best so far, or if its rarest token is rarer.
Tokens of `b` inside a run are not tried on their own.
*/
@(require_results)
histogram_best_run :: proc(h: ^Histogram, box: Box) -> (run: Box, found: Histogram_Found) {
a, b := h.a, h.b
count := h.count
first := h.first
next := h.next
// Chain the occurrences of each ID in the region of `a`, from the lowest index
for i := box.a_hi - 1; i >= box.a_lo; i -= 1 {
id := a[i]
next[i] = HISTOGRAM_NONE
if count[id] > 0 {
next[i] = first[id]
}
first[id] = i32(i)
count[id] += 1
}
best_count := HISTOGRAM_MAX_OCCURRENCES + 1
best_span := 0 // the best run's length in `a` minus one
has_common := false
has_run := false
for j := box.b_lo; j < box.b_hi; /**/ {
next_j := j + 1
c := int(count[b[j]])
switch {
case c == 0:
// Not in the region of `a`
case c > best_count:
has_common = true
case:
has_common = true
for i := int(first[b[j]]); i != HISTOGRAM_NONE; /**/ {
// Grow the run around `a[i] == b[j]`, noting its rarest token
rarest := c
a_lo, b_lo := i, j
for a_lo > box.a_lo && b_lo > box.b_lo && a[a_lo-1] == b[b_lo-1] {
a_lo -= 1
b_lo -= 1
if rarest > 1 {
rarest = min(rarest, int(count[a[a_lo]]))
}
}
a_last, b_last := i, j
for a_last < box.a_hi - 1 && b_last < box.b_hi - 1 && a[a_last+1] == b[b_last+1] {
a_last += 1
b_last += 1
if rarest > 1 {
rarest = min(rarest, int(count[a[a_last]]))
}
}
next_j = max(next_j, b_last + 1)
if a_last - a_lo > best_span || rarest < best_count {
run = {a_lo, a_last + 1, b_lo, b_last + 1, false}
best_span = a_last - a_lo
best_count = rarest
has_run = true
}
// The next occurrence past the run
i = int(next[i])
for i != HISTOGRAM_NONE && i <= a_last {
i = int(next[i])
}
}
}
j = next_j
}
// Leave the counts zeroed for the next region
for i in box.a_lo..<box.a_hi {
count[a[i]] = 0
}
switch {
case has_common && best_count > HISTOGRAM_MAX_OCCURRENCES:
found = .Fall_Back
case has_run:
found = .Run
case:
found = .Nothing
}
return
}