xcvr tui
at main 233 lines 5.7 kB view raw
1package main 2 3import () 4 5type EditType = int 6 7const ( 8 EditAdd EditType = iota 9 EditKeep 10 EditDel 11 EditNil 12) 13 14type Edit struct { 15 EditType EditType 16 Utf16Text []uint16 17} 18 19type Editstring struct { 20 EditType EditType 21 Text string 22} 23 24type EditSegment struct { 25 weight int 26 aidx int 27 bidx int 28 parent *EditSegment 29} 30 31type coordinate struct { 32 a int 33 b int 34} 35 36type SegmentHeap struct { 37 segments []*EditSegment 38 searched map[coordinate]bool 39} 40 41func NewSegmentHeap() SegmentHeap { 42 segments := make([]*EditSegment, 0, 10) 43 searched := make(map[coordinate]bool) 44 return SegmentHeap{segments, searched} 45} 46 47func (h *SegmentHeap) Add(seg *EditSegment) { 48 searched := h.searched[coordinate{seg.aidx, seg.bidx}] 49 if searched { 50 return 51 } 52 h.segments = append(h.segments, seg) 53 h.searched[coordinate{seg.aidx, seg.bidx}] = true 54 h.siftUp(len(h.segments) - 1) 55} 56 57func (h *SegmentHeap) PopFront() *EditSegment { 58 if len(h.segments) == 0 { 59 return nil 60 } 61 front := h.segments[0] 62 if len(h.segments) == 1 { 63 h.segments = nil 64 return front 65 } 66 h.segments[0] = h.segments[len(h.segments)-1] 67 h.segments = h.segments[:len(h.segments)-1] 68 h.siftDown(0) 69 return front 70} 71 72func (h *SegmentHeap) siftUp(idx int) { 73 if idx == 0 { 74 return 75 } 76 loweridx := idx 77 upperidx := (idx - 1) / 2 78 lower := h.segments[loweridx] 79 upper := h.segments[upperidx] 80 if lower.lighter(upper) { 81 h.segments[upperidx] = lower 82 h.segments[loweridx] = upper 83 h.siftUp(upperidx) 84 } 85} 86 87func (h *SegmentHeap) siftDown(idx int) { 88 upperidx := idx 89 var swap *EditSegment 90 loweridx := idx*2 + 1 91 lower2idx := idx*2 + 2 92 if loweridx < len(h.segments) && h.segments[loweridx].lighter(h.segments[upperidx]) { 93 swap = h.segments[upperidx] 94 h.segments[upperidx] = h.segments[loweridx] 95 h.segments[loweridx] = swap 96 h.siftDown(loweridx) 97 return 98 } 99 if lower2idx < len(h.segments) && h.segments[lower2idx].lighter(h.segments[upperidx]) { 100 swap = h.segments[upperidx] 101 h.segments[upperidx] = h.segments[lower2idx] 102 h.segments[lower2idx] = swap 103 h.siftDown(lower2idx) 104 return 105 } 106 107} 108 109func (seg *EditSegment) lighter(A *EditSegment) bool { 110 if seg.weight < A.weight { 111 return true 112 } else if seg.weight > A.weight { 113 return false 114 } else { 115 return seg.aidx+seg.bidx > A.aidx+A.bidx 116 } 117} 118 119// Diff calculates the diff between wordA and wordB as a miniaml slice of 120// edits that you have to make to wordA so that you end up with wordB 121func Diff(wordA []uint16, wordB []uint16) []Edit { 122 heap := NewSegmentHeap() 123 head := EditSegment{0, 0, 0, nil} 124 heap.Add(&head) 125 segment := heap.PopFront() 126 for !(segment.aidx == len(wordA) && segment.bidx == len(wordB)) { 127 if segment.aidx != len(wordA) && 128 segment.bidx != len(wordB) && 129 wordA[segment.aidx] == wordB[segment.bidx] { 130 newSegment := EditSegment{segment.weight, segment.aidx + 1, segment.bidx + 1, segment} 131 heap.Add(&newSegment) 132 } 133 if segment.aidx != len(wordA) { 134 newSegment := EditSegment{segment.weight + 1, segment.aidx + 1, segment.bidx, segment} 135 heap.Add(&newSegment) 136 } 137 if segment.bidx != len(wordB) { 138 newSegment := EditSegment{segment.weight + 1, segment.aidx, segment.bidx + 1, segment} 139 heap.Add(&newSegment) 140 } 141 segment = heap.PopFront() 142 } 143 prevSegment := segment.parent 144 edits := make([]Edit, 0) 145 currentEdit := Edit{EditNil, nil} 146 for prevSegment != nil { 147 diffA := prevSegment.aidx != segment.aidx 148 diffB := prevSegment.bidx != segment.bidx 149 var et EditType 150 var char uint16 151 if diffA && diffB { 152 et = EditKeep 153 char = wordA[prevSegment.aidx] 154 } else if diffA { 155 et = EditDel 156 char = wordA[prevSegment.aidx] 157 } else if diffB { 158 et = EditAdd 159 char = wordB[prevSegment.bidx] 160 } else { 161 et = EditNil 162 } 163 if currentEdit.EditType != et { 164 if currentEdit.EditType != EditNil { 165 edits = append([]Edit{currentEdit}, edits...) 166 } 167 currentEdit = Edit{et, []uint16{char}} 168 } else { 169 currentEdit.Utf16Text = append([]uint16{char}, currentEdit.Utf16Text...) 170 } 171 segment = prevSegment 172 prevSegment = segment.parent 173 } 174 edits = append([]Edit{currentEdit}, edits...) 175 return edits 176} 177 178func Diffs(wordA string, wordB string) []Editstring { 179 heap := NewSegmentHeap() 180 head := EditSegment{0, 0, 0, nil} 181 heap.Add(&head) 182 segment := heap.PopFront() 183 for !(segment.aidx == len(wordA) && segment.bidx == len(wordB)) { 184 if segment.aidx != len(wordA) && 185 segment.bidx != len(wordB) && 186 wordA[segment.aidx] == wordB[segment.bidx] { 187 newSegment := EditSegment{segment.weight, segment.aidx + 1, segment.bidx + 1, segment} 188 heap.Add(&newSegment) 189 } 190 if segment.aidx != len(wordA) { 191 newSegment := EditSegment{segment.weight + 1, segment.aidx + 1, segment.bidx, segment} 192 heap.Add(&newSegment) 193 } 194 if segment.bidx != len(wordB) { 195 newSegment := EditSegment{segment.weight + 1, segment.aidx, segment.bidx + 1, segment} 196 heap.Add(&newSegment) 197 } 198 segment = heap.PopFront() 199 } 200 prevSegment := segment.parent 201 edits := make([]Editstring, 0) 202 currentEdit := Editstring{EditNil, ""} 203 for prevSegment != nil { 204 diffA := prevSegment.aidx != segment.aidx 205 diffB := prevSegment.bidx != segment.bidx 206 var et EditType 207 var char string 208 if diffA && diffB { 209 et = EditKeep 210 char = string(wordA[prevSegment.aidx]) 211 } else if diffA { 212 et = EditDel 213 char = string(wordA[prevSegment.aidx]) 214 } else if diffB { 215 et = EditAdd 216 char = string(wordB[prevSegment.bidx]) 217 } else { 218 et = EditNil 219 } 220 if currentEdit.EditType != et { 221 if currentEdit.EditType != EditNil { 222 edits = append([]Editstring{currentEdit}, edits...) 223 } 224 currentEdit = Editstring{et, char} 225 } else { 226 currentEdit.Text = char + currentEdit.Text 227 } 228 segment = prevSegment 229 prevSegment = segment.parent 230 } 231 edits = append([]Editstring{currentEdit}, edits...) 232 return edits 233}