Skip to content

Commit db34c54

Browse files
committed
generalise with diff.Interface
BenchmarkDiff ~1500 ns/op BenchmarkDiffRunes ~1500 ns/op
1 parent f17504d commit db34c54

File tree

2 files changed

+99
-45
lines changed

2 files changed

+99
-45
lines changed

diff.go

Lines changed: 57 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -6,19 +6,63 @@
66
// The algorithm is described in "An O(ND) Difference Algorithm and its Variations", Eugene Myers, Algorithmica Vol. 1 No. 2, 1986, pp. 251-266.
77
package diff
88

9-
// Diff returns the differences between two int slices.
10-
func Diff(a, b []int) []Change {
11-
n := len(a)
12-
m := len(b)
13-
c := &context{a: a, b: b}
9+
// A type that satisfies diff.Interface can be diffed by this package.
10+
// It typically has two sequences A and B of comparable elements.
11+
type Interface interface {
12+
// N is the number of elements in A, called once
13+
N() int
14+
// M is the number of elements in B, called once
15+
M() int
16+
// Equal returns whether the elements at a and b are considered equal.
17+
// Called repeatedly with 0<=a<N and 0<=b<M
18+
Equal(a, b int) bool
19+
}
20+
21+
// Ints attaches diff.Interface methods to an array of two int slices
22+
type Ints [2][]int
23+
24+
func (i *Ints) N() int {
25+
return len(i[0])
26+
}
27+
func (i *Ints) M() int {
28+
return len(i[1])
29+
}
30+
func (i *Ints) Equal(a, b int) bool {
31+
return i[0][a] == i[1][b]
32+
}
33+
func (i *Ints) Diff() []Change {
34+
return Diff(i)
35+
}
36+
37+
// Runes attaches diff.Interface methods to an array of two rune slices
38+
type Runes [2][]rune
39+
40+
func (r *Runes) N() int {
41+
return len(r[0])
42+
}
43+
func (r *Runes) M() int {
44+
return len(r[1])
45+
}
46+
func (r *Runes) Equal(a, b int) bool {
47+
return r[0][a] == r[1][b]
48+
}
49+
func (r *Runes) Diff() []Change {
50+
return Diff(r)
51+
}
52+
53+
// Diff returns the differences of data.
54+
func Diff(data Interface) []Change {
55+
n := data.N()
56+
m := data.M()
57+
c := &context{data: data}
1458
if n > m {
1559
c.flags = make([]byte, n)
1660
} else {
1761
c.flags = make([]byte, m)
1862
}
1963
c.max = n + m + 1
2064
c.compare(0, 0, n, m)
21-
return c.result()
65+
return c.result(n, m)
2266
}
2367

2468
// A Change contains one or more deletions or inserts
@@ -30,7 +74,7 @@ type Change struct {
3074
}
3175

3276
type context struct {
33-
a, b []int // inputs
77+
data Interface
3478
flags []byte // element bits 1 delete, 2 insert
3579
max int
3680
// forward and reverse d-path endpoint x components
@@ -39,12 +83,12 @@ type context struct {
3983

4084
func (c *context) compare(aoffset, boffset, alimit, blimit int) {
4185
// eat common prefix
42-
for aoffset < alimit && boffset < blimit && c.a[aoffset] == c.b[boffset] {
86+
for aoffset < alimit && boffset < blimit && c.data.Equal(aoffset, boffset) {
4387
aoffset++
4488
boffset++
4589
}
4690
// eat common suffix
47-
for alimit > aoffset && blimit > boffset && c.a[alimit-1] == c.b[blimit-1] {
91+
for alimit > aoffset && blimit > boffset && c.data.Equal(alimit-1, blimit-1) {
4892
alimit--
4993
blimit--
5094
}
@@ -68,6 +112,7 @@ func (c *context) compare(aoffset, boffset, alimit, blimit int) {
68112
c.compare(aoffset, boffset, x, y)
69113
c.compare(x, y, alimit, blimit)
70114
}
115+
71116
func (c *context) findMiddleSnake(aoffset, boffset, alimit, blimit int) (int, int) {
72117
// midpoints
73118
fmid := aoffset - boffset
@@ -94,7 +139,7 @@ func (c *context) findMiddleSnake(aoffset, boffset, alimit, blimit int) (int, in
94139
x = c.forward[foff+k-1] + 1 // right
95140
}
96141
y = x - k
97-
for x < alimit && y < blimit && c.a[x] == c.b[y] {
142+
for x < alimit && y < blimit && c.data.Equal(x, y) {
98143
x++
99144
y++
100145
}
@@ -113,7 +158,7 @@ func (c *context) findMiddleSnake(aoffset, boffset, alimit, blimit int) (int, in
113158
x = c.reverse[roff+k+1] - 1 // left
114159
}
115160
y = x - k
116-
for x > aoffset && y > boffset && c.a[x-1] == c.b[y-1] {
161+
for x > aoffset && y > boffset && c.data.Equal(x-1, y-1) {
117162
x--
118163
y--
119164
}
@@ -130,10 +175,8 @@ func (c *context) findMiddleSnake(aoffset, boffset, alimit, blimit int) (int, in
130175
panic("should never be reached")
131176
}
132177

133-
func (c *context) result() (res []Change) {
178+
func (c *context) result(n, m int) (res []Change) {
134179
var x, y int
135-
n := len(c.a)
136-
m := len(c.b)
137180
for x < n || y < m {
138181
if x < n && y < m && c.flags[x]&1 == 0 && c.flags[y]&2 == 0 {
139182
x++

diff_test.go

Lines changed: 42 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -10,54 +10,70 @@ import (
1010

1111
type testcase struct {
1212
name string
13-
a, b []int
13+
data *Ints
1414
res []Change
1515
}
1616

1717
var tests = []testcase{
1818
{"shift",
19-
[]int{1, 2, 3},
20-
[]int{0, 1, 2, 3},
19+
&Ints{
20+
{1, 2, 3},
21+
{0, 1, 2, 3},
22+
},
2123
[]Change{{0, 0, 0, 1}},
2224
},
2325
{"push",
24-
[]int{1, 2, 3},
25-
[]int{1, 2, 3, 4},
26+
&Ints{
27+
{1, 2, 3},
28+
{1, 2, 3, 4},
29+
},
2630
[]Change{{3, 3, 0, 1}},
2731
},
2832
{"unshift",
29-
[]int{0, 1, 2, 3},
30-
[]int{1, 2, 3},
33+
&Ints{
34+
{0, 1, 2, 3},
35+
{1, 2, 3},
36+
},
3137
[]Change{{0, 0, 1, 0}},
3238
},
3339
{"pop",
34-
[]int{1, 2, 3, 4},
35-
[]int{1, 2, 3},
40+
&Ints{
41+
{1, 2, 3, 4},
42+
{1, 2, 3},
43+
},
3644
[]Change{{3, 3, 1, 0}},
3745
},
3846
{"all changed",
39-
[]int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9},
40-
[]int{10, 11, 12, 13, 14},
47+
&Ints{
48+
{0, 1, 2, 3, 4, 5, 6, 7, 8, 9},
49+
{10, 11, 12, 13, 14},
50+
},
4151
[]Change{
4252
{0, 0, 10, 5},
4353
},
4454
},
4555
{"all same",
46-
[]int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9},
47-
[]int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9},
56+
&Ints{
57+
{0, 1, 2, 3, 4, 5, 6, 7, 8, 9},
58+
{0, 1, 2, 3, 4, 5, 6, 7, 8, 9},
59+
},
4860
[]Change{},
4961
},
5062
{"wrap",
51-
[]int{1},
52-
[]int{0, 1, 2, 3},
63+
&Ints{
64+
{1},
65+
{0, 1, 2, 3},
66+
},
5367
[]Change{
5468
{0, 0, 0, 1},
5569
{1, 2, 0, 2},
5670
},
5771
},
5872
{"snake",
59-
[]int{0, 1, 2, 3, 4, 5},
60-
[]int{1, 2, 3, 4, 5, 6},
73+
&Ints{
74+
{0, 1, 2, 3, 4, 5},
75+
{1, 2, 3, 4, 5, 6},
76+
},
6177
[]Change{
6278
{0, 0, 1, 0},
6379
{6, 5, 0, 1},
@@ -67,8 +83,10 @@ var tests = []testcase{
6783
// first two traces differ from fig.1
6884
// it still is a lcs and ses path
6985
{"paper fig. 1",
70-
[]int{1, 2, 3, 1, 2, 2, 1},
71-
[]int{3, 2, 1, 2, 1, 3},
86+
&Ints{
87+
{1, 2, 3, 1, 2, 2, 1},
88+
{3, 2, 1, 2, 1, 3},
89+
},
7290
[]Change{
7391
{0, 0, 1, 1},
7492
{2, 2, 1, 0},
@@ -80,7 +98,7 @@ var tests = []testcase{
8098

8199
func TestDiffAB(t *testing.T) {
82100
for _, test := range tests {
83-
res := Diff(test.a, test.b)
101+
res := test.data.Diff()
84102
if len(res) != len(test.res) {
85103
t.Error(test.name, "expected length", len(test.res), "for", res)
86104
continue
@@ -102,7 +120,7 @@ func TestDiffBA(t *testing.T) {
102120
{7, 5, 0, 1},
103121
}
104122
for _, test := range tests {
105-
res := Diff(test.b, test.a)
123+
res := Diff(&Ints{test.data[1], test.data[0]})
106124
if len(res) != len(test.res) {
107125
t.Error(test.name, "expected length", len(test.res), "for", res)
108126
continue
@@ -120,20 +138,13 @@ func TestDiffBA(t *testing.T) {
120138
func BenchmarkDiff(b *testing.B) {
121139
t := tests[len(tests)-1]
122140
for i := 0; i < b.N; i++ {
123-
Diff(t.a, t.b)
141+
t.data.Diff()
124142
}
125143
}
126144

127145
func BenchmarkDiffRunes(b *testing.B) {
128-
ta := make([]int, 7)
129-
for _, r := range "1231221" {
130-
ta = append(ta, int(r))
131-
}
132-
tb := make([]int, 6)
133-
for _, r := range "321213" {
134-
tb = append(tb, int(r))
135-
}
146+
data := &Runes{[]rune("1231221"), []rune("321213")}
136147
for i := 0; i < b.N; i++ {
137-
Diff(ta, tb)
148+
Diff(data)
138149
}
139150
}

0 commit comments

Comments
 (0)