docdiff 0.5.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +6 -0
- data/.travis.yml +7 -0
- data/Gemfile +17 -0
- data/Guardfile +8 -0
- data/Makefile +108 -0
- data/Rakefile +17 -0
- data/bin/docdiff +179 -0
- data/devutil/JIS0208.TXT +6952 -0
- data/devutil/char_by_charclass.rb +23 -0
- data/devutil/charclass_by_char.rb +21 -0
- data/devutil/jis0208.rb +343 -0
- data/devutil/testjis0208.rb +38 -0
- data/docdiff.conf.example +22 -0
- data/docdiff.gemspec +23 -0
- data/docdiffwebui.cgi +176 -0
- data/docdiffwebui.html +123 -0
- data/img/docdiff-screenshot-format-html-digest-firefox.png +0 -0
- data/img/docdiff-screenshot-format-html-firefox.png +0 -0
- data/img/docdiff-screenshot-format-tty-cmdexe-en.png +0 -0
- data/img/docdiff-screenshot-format-tty-cmdexe-ja.png +0 -0
- data/img/docdiff-screenshot-format-tty-rxvtunicode-en.png +0 -0
- data/img/docdiff-screenshot-format-tty-rxvtunicode-ja.png +0 -0
- data/img/docdiff-screenshot-format-tty-xterm-en.png +0 -0
- data/img/docdiff-screenshot-format-tty-xterm-ja.png +0 -0
- data/img/docdiff-screenshot-resolution-linewordchar-xterm.png +0 -0
- data/index.html +181 -0
- data/langfilter.rb +14 -0
- data/lib/doc_diff.rb +170 -0
- data/lib/docdiff.rb +7 -0
- data/lib/docdiff/charstring.rb +579 -0
- data/lib/docdiff/diff.rb +217 -0
- data/lib/docdiff/diff/contours.rb +382 -0
- data/lib/docdiff/diff/editscript.rb +148 -0
- data/lib/docdiff/diff/rcsdiff.rb +107 -0
- data/lib/docdiff/diff/shortestpath.rb +93 -0
- data/lib/docdiff/diff/speculative.rb +40 -0
- data/lib/docdiff/diff/subsequence.rb +39 -0
- data/lib/docdiff/diff/unidiff.rb +124 -0
- data/lib/docdiff/difference.rb +92 -0
- data/lib/docdiff/document.rb +127 -0
- data/lib/docdiff/encoding/en_ascii.rb +97 -0
- data/lib/docdiff/encoding/ja_eucjp.rb +269 -0
- data/lib/docdiff/encoding/ja_sjis.rb +260 -0
- data/lib/docdiff/encoding/ja_utf8.rb +6974 -0
- data/lib/docdiff/version.rb +3 -0
- data/lib/docdiff/view.rb +476 -0
- data/lib/viewdiff.rb +375 -0
- data/readme.html +713 -0
- data/sample/01.en.ascii.cr +1 -0
- data/sample/01.en.ascii.crlf +2 -0
- data/sample/01.en.ascii.lf +2 -0
- data/sample/01.ja.eucjp.lf +2 -0
- data/sample/01.ja.sjis.cr +1 -0
- data/sample/01.ja.sjis.crlf +2 -0
- data/sample/01.ja.utf8.crlf +2 -0
- data/sample/02.en.ascii.cr +1 -0
- data/sample/02.en.ascii.crlf +2 -0
- data/sample/02.en.ascii.lf +2 -0
- data/sample/02.ja.eucjp.lf +2 -0
- data/sample/02.ja.sjis.cr +1 -0
- data/sample/02.ja.sjis.crlf +2 -0
- data/sample/02.ja.utf8.crlf +2 -0
- data/sample/humpty_dumpty01.ascii.lf +4 -0
- data/sample/humpty_dumpty02.ascii.lf +4 -0
- data/test/charstring_test.rb +1008 -0
- data/test/diff_test.rb +36 -0
- data/test/difference_test.rb +64 -0
- data/test/docdiff_test.rb +193 -0
- data/test/document_test.rb +626 -0
- data/test/test_helper.rb +7 -0
- data/test/view_test.rb +570 -0
- data/test/viewdiff_test.rb +908 -0
- metadata +129 -0
data/lib/docdiff/diff.rb
ADDED
@@ -0,0 +1,217 @@
|
|
1
|
+
=begin
|
2
|
+
= Diff
|
3
|
+
--- Diff.new(seq_a, seq_b)
|
4
|
+
--- Diff#ses([algorithm=:speculative])
|
5
|
+
--- Diff#lcs([algorithm=:speculative])
|
6
|
+
|
7
|
+
Available algorithms are follows.
|
8
|
+
* :shortestpath
|
9
|
+
* :contours
|
10
|
+
* :speculative
|
11
|
+
|
12
|
+
= Diff::EditScript
|
13
|
+
--- Diff::EditScript.new
|
14
|
+
--- Diff::EditScript#del(seq_or_len_a)
|
15
|
+
--- Diff::EditScript#add(seq_or_len_b)
|
16
|
+
--- Diff::EditScript#common(seq_or_len_a[, seq_or_len_b])
|
17
|
+
--- Diff::EditScript#commonsubsequence
|
18
|
+
--- Diff::EditScript#count_a
|
19
|
+
--- Diff::EditScript#count_b
|
20
|
+
--- Diff::EditScript#additions
|
21
|
+
--- Diff::EditScript#deletions
|
22
|
+
--- Diff::EditScript#each {|mark, a, b| ...}
|
23
|
+
--- Diff::EditScript#apply(arr)
|
24
|
+
--- Diff::EditScript.parse_rcsdiff(input)
|
25
|
+
--- Diff::EditScript#rcsdiff([out=''])
|
26
|
+
|
27
|
+
= Diff::Subsequence
|
28
|
+
--- Diff::Subsequence.new
|
29
|
+
--- Diff::Subsequence.add(i, j[, len=1])
|
30
|
+
--- Diff::Subsequence#length
|
31
|
+
--- Diff::Subsequence#each {|i, j, len| ...}
|
32
|
+
=end
|
33
|
+
|
34
|
+
require 'docdiff/diff/editscript'
|
35
|
+
require 'docdiff/diff/subsequence'
|
36
|
+
require 'docdiff/diff/shortestpath'
|
37
|
+
require 'docdiff/diff/contours'
|
38
|
+
require 'docdiff/diff/speculative'
|
39
|
+
|
40
|
+
=begin
|
41
|
+
Data class reduces input for diff and convert alphabet to Integer.
|
42
|
+
|
43
|
+
It reduces input by removing common prefix, suffix and
|
44
|
+
unique elements.
|
45
|
+
|
46
|
+
So, reduced input has following properties:
|
47
|
+
* First element is different.
|
48
|
+
* Last element is different.
|
49
|
+
* Any elemnt in A is also exist in B.
|
50
|
+
* Any elemnt in B is also exist in A.
|
51
|
+
|
52
|
+
=end
|
53
|
+
class Diff
|
54
|
+
def initialize(a, b)
|
55
|
+
@original_a = a
|
56
|
+
@original_b = b
|
57
|
+
|
58
|
+
count_a = {}
|
59
|
+
count_a.default = 0
|
60
|
+
a.each {|e| count_a[e] += 1}
|
61
|
+
|
62
|
+
count_b = {}
|
63
|
+
count_b.default = 0
|
64
|
+
b.each {|e| count_b[e] += 1}
|
65
|
+
|
66
|
+
beg_a = 0
|
67
|
+
end_a = a.length
|
68
|
+
|
69
|
+
beg_b = 0
|
70
|
+
end_b = b.length
|
71
|
+
|
72
|
+
@prefix_lcs = []
|
73
|
+
@suffix_lcs = []
|
74
|
+
|
75
|
+
flag = true
|
76
|
+
while flag
|
77
|
+
flag = false
|
78
|
+
|
79
|
+
while beg_a < end_a && beg_b < end_b && a[beg_a].eql?(b[beg_b])
|
80
|
+
@prefix_lcs << [beg_a, beg_b]
|
81
|
+
count_a[a[beg_a]] -= 1
|
82
|
+
count_b[b[beg_b]] -= 1
|
83
|
+
beg_a += 1
|
84
|
+
beg_b += 1
|
85
|
+
flag = true
|
86
|
+
end
|
87
|
+
|
88
|
+
while beg_a < end_a && beg_b < end_b && a[end_a - 1].eql?(b[end_b - 1])
|
89
|
+
@suffix_lcs << [end_a - 1, end_b - 1]
|
90
|
+
count_a[a[end_a - 1]] -= 1
|
91
|
+
count_b[b[end_b - 1]] -= 1
|
92
|
+
end_a -= 1
|
93
|
+
end_b -= 1
|
94
|
+
flag = true
|
95
|
+
end
|
96
|
+
|
97
|
+
while beg_a < end_a && count_b[a[beg_a]] == 0
|
98
|
+
count_a[a[beg_a]] -= 1
|
99
|
+
beg_a += 1
|
100
|
+
flag = true
|
101
|
+
end
|
102
|
+
|
103
|
+
while beg_b < end_b && count_a[b[beg_b]] == 0
|
104
|
+
count_b[b[beg_b]] -= 1
|
105
|
+
beg_b += 1
|
106
|
+
flag = true
|
107
|
+
end
|
108
|
+
|
109
|
+
while beg_a < end_a && count_b[a[end_a - 1]] == 0
|
110
|
+
count_a[a[end_a - 1]] -= 1
|
111
|
+
end_a -= 1
|
112
|
+
flag = true
|
113
|
+
end
|
114
|
+
|
115
|
+
while beg_b < end_b && count_a[b[end_b - 1]] == 0
|
116
|
+
count_b[b[end_b - 1]] -= 1
|
117
|
+
end_b -= 1
|
118
|
+
flag = true
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
@alphabet = Alphabet.new
|
123
|
+
|
124
|
+
@a = []
|
125
|
+
@revert_index_a = []
|
126
|
+
(beg_a...end_a).each {|i|
|
127
|
+
if count_b[a[i]] != 0
|
128
|
+
@a << @alphabet.add(a[i])
|
129
|
+
@revert_index_a << i
|
130
|
+
end
|
131
|
+
}
|
132
|
+
|
133
|
+
@b = []
|
134
|
+
@revert_index_b = []
|
135
|
+
(beg_b...end_b).each {|i|
|
136
|
+
if count_a[b[i]] != 0
|
137
|
+
@b << @alphabet.add(b[i])
|
138
|
+
@revert_index_b << i
|
139
|
+
end
|
140
|
+
}
|
141
|
+
end
|
142
|
+
|
143
|
+
def Diff.algorithm(algorithm)
|
144
|
+
case algorithm
|
145
|
+
when :shortestpath
|
146
|
+
return ShortestPath
|
147
|
+
when :contours
|
148
|
+
return Contours
|
149
|
+
when :speculative
|
150
|
+
return Speculative
|
151
|
+
else
|
152
|
+
raise ArgumentError.new("unknown diff algorithm: #{algorithm}")
|
153
|
+
end
|
154
|
+
end
|
155
|
+
|
156
|
+
def lcs(algorithm=:speculative) # longest common subsequence
|
157
|
+
klass = Diff.algorithm(algorithm)
|
158
|
+
reduced_lcs = klass.new(@a, @b).lcs
|
159
|
+
|
160
|
+
lcs = Subsequence.new
|
161
|
+
@prefix_lcs.each {|i, j| lcs.add i, j}
|
162
|
+
reduced_lcs.each {|i, j, l|
|
163
|
+
l.times {|k|
|
164
|
+
lcs.add @revert_index_a[i+k], @revert_index_b[j+k]
|
165
|
+
}
|
166
|
+
}
|
167
|
+
@suffix_lcs.reverse_each {|i, j| lcs.add i, j}
|
168
|
+
|
169
|
+
return lcs
|
170
|
+
end
|
171
|
+
|
172
|
+
def ses(algorithm=nil) # shortest edit script
|
173
|
+
algorithm ||= :speculative
|
174
|
+
lcs = lcs(algorithm)
|
175
|
+
ses = EditScript.new
|
176
|
+
i0 = j0 = 0
|
177
|
+
lcs.each {|i, j, l|
|
178
|
+
ses.del @original_a[i0, i - i0] if i0 < i
|
179
|
+
ses.add @original_b[j0, j - j0] if j0 < j
|
180
|
+
ses.common @original_a[i, l], @original_b[j, l]
|
181
|
+
|
182
|
+
i0 = i + l
|
183
|
+
j0 = j + l
|
184
|
+
}
|
185
|
+
|
186
|
+
i = @original_a.length
|
187
|
+
j = @original_b.length
|
188
|
+
ses.del @original_a[i0, i - i0] if i0 < i
|
189
|
+
ses.add @original_b[j0, j - j0] if j0 < j
|
190
|
+
|
191
|
+
return ses
|
192
|
+
end
|
193
|
+
|
194
|
+
class Alphabet
|
195
|
+
def initialize
|
196
|
+
@hash = {}
|
197
|
+
end
|
198
|
+
|
199
|
+
def add(v)
|
200
|
+
if @hash.include? v
|
201
|
+
return @hash[v]
|
202
|
+
else
|
203
|
+
return @hash[v] = @hash.size
|
204
|
+
end
|
205
|
+
end
|
206
|
+
|
207
|
+
class NoSymbol < StandardError
|
208
|
+
end
|
209
|
+
def index(v)
|
210
|
+
return @hash.fetch {raise NoSymbol.new(v.to_s)}
|
211
|
+
end
|
212
|
+
|
213
|
+
def size
|
214
|
+
return @hash.size
|
215
|
+
end
|
216
|
+
end
|
217
|
+
end
|
@@ -0,0 +1,382 @@
|
|
1
|
+
=begin
|
2
|
+
== Contours
|
3
|
+
Contours is based on the algorithm which is presented by Claus Rick.
|
4
|
+
|
5
|
+
I made two optimizations (for long LCS):
|
6
|
+
|
7
|
+
* When a midpoint of LCS is found, adjacent matches on same diagonal is checked.
|
8
|
+
They are also part of LCS. If LCS is long, they may exist and even long.
|
9
|
+
|
10
|
+
* Search method for next contour uses divide and conquer.
|
11
|
+
|
12
|
+
* Search region is rectangle: (This is forward contour case.)
|
13
|
+
|
14
|
+
(min{i|(i,j) in dominants}, min{j|(i,j) in dominants}) to (end_a, end_b).
|
15
|
+
|
16
|
+
* In search region (i0,j0) to (i1,j1), For each dominant match (i,j) in
|
17
|
+
Ck and the region, (i+1,j+1) is checked first. If LCS is long it is
|
18
|
+
match frequently.
|
19
|
+
If it is match, it's a match in Ck+1 and it divides search region:
|
20
|
+
|
21
|
+
(i0,j) to (i+1,end_b)
|
22
|
+
(i,j0) to (end_a,j+1)
|
23
|
+
|
24
|
+
* For each divided region, dominants is searchd line by line:
|
25
|
+
topmost row or leftmost column. Longer one is selected.
|
26
|
+
|
27
|
+
If no dominant match is found in the line,
|
28
|
+
search region is reduced with only the line.
|
29
|
+
|
30
|
+
If a dominant match is found in the line,
|
31
|
+
search region is reduced with the line and
|
32
|
+
rectangle farer than the match.
|
33
|
+
|
34
|
+
== References
|
35
|
+
[Claus2000] Claus Rick,
|
36
|
+
Simple and Fast Linear Space Computation of Longest Common Subsequences,
|
37
|
+
Information Processing Letters, Vol. 75/6, 275 - 281,
|
38
|
+
Elsevier (2000)
|
39
|
+
|
40
|
+
[Claus1995] Claus Rick,
|
41
|
+
A New Flexible Algorithm for the Longest Common Subsequence Problem,
|
42
|
+
Proceedings of the 6th Symposium on Combinatorial Pattern Matching (CPM'95),
|
43
|
+
Lecture Notes in Computer Science, Vol. 937, 340 - 351,
|
44
|
+
Springer Verlag (1995)
|
45
|
+
Also in Nordic Journal of Computing (NJC), Vol. 2, No. 4, Winter 1995, 444 - 461.
|
46
|
+
http://web.informatik.uni-bonn.de/IV/Mitarbeiter/rick/lcs.dvi.Z
|
47
|
+
=end
|
48
|
+
|
49
|
+
class Diff
|
50
|
+
class Contours
|
51
|
+
def initialize(a, b)
|
52
|
+
@a = a
|
53
|
+
@b = b
|
54
|
+
@closest_a = Closest.new(@a)
|
55
|
+
@closest_b = Closest.new(@b)
|
56
|
+
end
|
57
|
+
|
58
|
+
def lcs(lcs=Subsequence.new, beg_a=0, beg_b=0, end_a=@a.length, end_b=@b.length, len=nil)
|
59
|
+
#p [:lcs, beg_a, beg_b, end_a, end_b]
|
60
|
+
found, len, mid_a, mid_b = midpoint(beg_a, beg_b, end_a, end_b, len)
|
61
|
+
|
62
|
+
return lcs unless found
|
63
|
+
|
64
|
+
len1 = len2 = len / 2
|
65
|
+
if len & 1 == 0
|
66
|
+
len2 -= 1
|
67
|
+
end
|
68
|
+
|
69
|
+
l = 1
|
70
|
+
|
71
|
+
while beg_a < mid_a && beg_b < mid_b && @a[mid_a-1] == @b[mid_b-1]
|
72
|
+
len1 -= 1
|
73
|
+
mid_a -= 1
|
74
|
+
mid_b -= 1
|
75
|
+
l += 1
|
76
|
+
end
|
77
|
+
|
78
|
+
while mid_a+l < end_a && mid_b+l < end_b && @a[mid_a+l] == @b[mid_b+l]
|
79
|
+
len2 -= 1
|
80
|
+
l += 1
|
81
|
+
end
|
82
|
+
|
83
|
+
lcs(lcs, beg_a, beg_b, mid_a, mid_b, len1)
|
84
|
+
lcs.add(mid_a, mid_b, l)
|
85
|
+
lcs(lcs, mid_a + l, mid_b + l, end_a, end_b, len2)
|
86
|
+
|
87
|
+
return lcs
|
88
|
+
end
|
89
|
+
|
90
|
+
def midpoint(beg_a, beg_b, end_a, end_b, len)
|
91
|
+
return false, 0, nil, nil if len == 0
|
92
|
+
|
93
|
+
fc = newForwardContour(beg_a, beg_b, end_a, end_b)
|
94
|
+
return false, 0, nil, nil if fc.empty?
|
95
|
+
|
96
|
+
bc = newBackwardContour(beg_a, beg_b, end_a, end_b)
|
97
|
+
|
98
|
+
midpoints = nil
|
99
|
+
|
100
|
+
l = 1
|
101
|
+
|
102
|
+
while true
|
103
|
+
crossed = contourCrossed(fc, bc)
|
104
|
+
if crossed
|
105
|
+
midpoints = fc
|
106
|
+
break
|
107
|
+
end
|
108
|
+
l += 1
|
109
|
+
fc = nextForwardContour(fc, end_a, end_b)
|
110
|
+
crossed = contourCrossed(fc, bc)
|
111
|
+
if crossed
|
112
|
+
midpoints = bc
|
113
|
+
break
|
114
|
+
end
|
115
|
+
l += 1
|
116
|
+
bc = nextBackwardContour(bc, beg_a, beg_b)
|
117
|
+
end
|
118
|
+
|
119
|
+
# select a dominant match which is closest to diagonal.
|
120
|
+
m = midpoints[0]
|
121
|
+
(1...midpoints.length).each {|m1| m = m1 if m[0] < m1[0] && m[1] < m1[1] }
|
122
|
+
|
123
|
+
return [true, l, *m]
|
124
|
+
end
|
125
|
+
|
126
|
+
def newForwardContour(beg_a, beg_b, end_a, end_b)
|
127
|
+
return nextForwardContour([[beg_a-1,beg_b-1]], end_a, end_b)
|
128
|
+
end
|
129
|
+
|
130
|
+
def nextForwardContour(fc0, end_a, end_b)
|
131
|
+
next_dominants = []
|
132
|
+
topright_dominant = 0
|
133
|
+
bottomleft_dominant = fc0.length - 1
|
134
|
+
|
135
|
+
fc0.each_index {|k|
|
136
|
+
i, j = fc0[k]
|
137
|
+
if i+1 < end_a && j+1 < end_b && @a[i+1] == @b[j+1]
|
138
|
+
if topright_dominant <= k - 1
|
139
|
+
nextForwardContour1(fc0, topright_dominant, k - 1, i+1, end_b, next_dominants)
|
140
|
+
end
|
141
|
+
next_dominants << [i+1, j+1]
|
142
|
+
end_b = j+1
|
143
|
+
topright_dominant = k + 1
|
144
|
+
end
|
145
|
+
}
|
146
|
+
|
147
|
+
if topright_dominant <= bottomleft_dominant
|
148
|
+
nextForwardContour1(fc0, topright_dominant, bottomleft_dominant, end_a, end_b, next_dominants)
|
149
|
+
end
|
150
|
+
return next_dominants
|
151
|
+
end
|
152
|
+
|
153
|
+
def nextForwardContour1(fc0, topright_dominant, bottomleft_dominant, end_a, end_b, next_dominants_topright)
|
154
|
+
beg_a = fc0[topright_dominant][0] + 1
|
155
|
+
beg_b = fc0[bottomleft_dominant][1] + 1
|
156
|
+
|
157
|
+
next_dominants_bottomleft = []
|
158
|
+
|
159
|
+
while beg_a < end_a && beg_b < end_b
|
160
|
+
if end_a - beg_a < end_b - beg_b
|
161
|
+
# search top row: [beg_a, beg_b] to [beg_a, end_b-1] inclusive
|
162
|
+
if topright_dominant + 1 < fc0.length && fc0[topright_dominant + 1][0] < beg_a
|
163
|
+
topright_dominant += 1
|
164
|
+
end
|
165
|
+
search_start_b = fc0[topright_dominant][1]
|
166
|
+
# search top row: [beg_a, search_start_b+1] to [beg_a, end_b-1] inclusive
|
167
|
+
j = @closest_b.next(@a[beg_a], search_start_b)
|
168
|
+
if j < end_b
|
169
|
+
# new dominant found.
|
170
|
+
# it means that the rectangle [beg_a, j] to [end_a-1, end_b-1] is not required to search any more.
|
171
|
+
next_dominants_topright << [beg_a, j]
|
172
|
+
end_b = j
|
173
|
+
end
|
174
|
+
beg_a += 1
|
175
|
+
else
|
176
|
+
# search left column: [beg_a, beg_b] to [end_a-1, beg_b]
|
177
|
+
if 0 <= bottomleft_dominant - 1 && fc0[bottomleft_dominant - 1][1] < beg_b
|
178
|
+
bottomleft_dominant -= 1
|
179
|
+
end
|
180
|
+
search_start_a = fc0[bottomleft_dominant][0]
|
181
|
+
# search left column: [search_start_a, beg_b] to [end_a-1, beg_b]
|
182
|
+
i = @closest_a.next(@b[beg_b], search_start_a)
|
183
|
+
if i < end_a
|
184
|
+
# new dominant found.
|
185
|
+
# if means that the rectangle [i, beg_b] to [end_a-1, end_b-1] is not required to search any more.
|
186
|
+
next_dominants_bottomleft << [i, beg_b]
|
187
|
+
end_a = i
|
188
|
+
end
|
189
|
+
beg_b += 1
|
190
|
+
end
|
191
|
+
end
|
192
|
+
|
193
|
+
next_dominants_bottomleft.reverse!
|
194
|
+
next_dominants_topright.concat next_dominants_bottomleft
|
195
|
+
end
|
196
|
+
|
197
|
+
def newBackwardContour(beg_a, beg_b, end_a, end_b)
|
198
|
+
return nextBackwardContour([[end_a,end_b]], beg_a, beg_b)
|
199
|
+
end
|
200
|
+
|
201
|
+
def nextBackwardContour(bc0, beg_a, beg_b)
|
202
|
+
next_dominants = []
|
203
|
+
topright_dominant = 0
|
204
|
+
bottomleft_dominant = bc0.length - 1
|
205
|
+
|
206
|
+
bc0.each_index {|k|
|
207
|
+
i, j = bc0[k]
|
208
|
+
if beg_a <= i-1 && beg_b <= j-1 && @a[i-1] == @b[j-1]
|
209
|
+
if topright_dominant <= k - 1
|
210
|
+
nextBackwardContour1(bc0, topright_dominant, k - 1, beg_a, j, next_dominants)
|
211
|
+
end
|
212
|
+
next_dominants << [i-1, j-1]
|
213
|
+
beg_a = i
|
214
|
+
topright_dominant = k + 1
|
215
|
+
end
|
216
|
+
}
|
217
|
+
|
218
|
+
if topright_dominant <= bottomleft_dominant
|
219
|
+
nextBackwardContour1(bc0, topright_dominant, bottomleft_dominant, beg_a, beg_b, next_dominants)
|
220
|
+
end
|
221
|
+
return next_dominants
|
222
|
+
end
|
223
|
+
|
224
|
+
def nextBackwardContour1(bc0, topright_dominant, bottomleft_dominant, beg_a, beg_b, next_dominants_topright)
|
225
|
+
end_a = bc0[bottomleft_dominant][0]
|
226
|
+
end_b = bc0[topright_dominant][1]
|
227
|
+
|
228
|
+
next_dominants_bottomleft = []
|
229
|
+
|
230
|
+
while beg_a < end_a && beg_b < end_b
|
231
|
+
if end_a - beg_a < end_b - beg_b
|
232
|
+
# search bottom row: [end_a-1, end_b-1] from [end_a-1, beg_b]
|
233
|
+
if 0 <= bottomleft_dominant - 1 && end_a - 1 < bc0[bottomleft_dominant - 1][0]
|
234
|
+
bottomleft_dominant -= 1
|
235
|
+
end
|
236
|
+
search_end_b = bc0[bottomleft_dominant][1]
|
237
|
+
# search bottom row: [end_a-1, search_end_b-1] from [end_a-1, beg_b]
|
238
|
+
j = @closest_b.prev(@a[end_a-1], search_end_b)
|
239
|
+
if beg_b <= j
|
240
|
+
# new dominant found.
|
241
|
+
# it means that the rectangle [beg_a, beg_b] to [end_a-1, j] is not required to search any more.
|
242
|
+
next_dominants_bottomleft << [end_a-1, j]
|
243
|
+
beg_b = j + 1
|
244
|
+
end
|
245
|
+
end_a -= 1
|
246
|
+
else
|
247
|
+
# search right column: [end_a-1, end_b-1] to [beg_a, end_b-1]
|
248
|
+
if topright_dominant + 1 < bc0.length && end_b - 1 < bc0[topright_dominant + 1][1]
|
249
|
+
topright_dominant += 1
|
250
|
+
end
|
251
|
+
search_end_a = bc0[topright_dominant][0]
|
252
|
+
# search right column: [search_end_a-1, end_b-1] to [beg_a, end_b-1]
|
253
|
+
i = @closest_a.prev(@b[end_b-1], search_end_a)
|
254
|
+
if beg_a <= i
|
255
|
+
# new dominant found.
|
256
|
+
# if means that the rectangle [beg_a, beg_b] to [i, end_b-1] is not required to search any more.
|
257
|
+
next_dominants_topright << [i, end_b-1]
|
258
|
+
beg_a = i + 1
|
259
|
+
end
|
260
|
+
end_b -= 1
|
261
|
+
end
|
262
|
+
end
|
263
|
+
|
264
|
+
next_dominants_bottomleft.reverse!
|
265
|
+
next_dominants_topright.concat next_dominants_bottomleft
|
266
|
+
end
|
267
|
+
|
268
|
+
def contourCrossed(fc, bc)
|
269
|
+
#p [:contourCrossed1Beg, fc, bc]
|
270
|
+
new_fc, new_bc = contourCrossed1(fc, bc)
|
271
|
+
#p [:contourCrossed1End, new_fc, new_bc]
|
272
|
+
if new_fc.empty? && new_bc.empty?
|
273
|
+
return true
|
274
|
+
end
|
275
|
+
|
276
|
+
fc.replace new_fc
|
277
|
+
bc.replace new_bc
|
278
|
+
|
279
|
+
return false
|
280
|
+
end
|
281
|
+
|
282
|
+
def contourCrossed1(fc, bc)
|
283
|
+
new_fc = []
|
284
|
+
new_bc = []
|
285
|
+
fc_k = 0
|
286
|
+
bc_k = 0
|
287
|
+
bc_j = bc[0][1]
|
288
|
+
fc_j = fc[0][1]
|
289
|
+
fc_j = bc_j if fc_j < bc_j
|
290
|
+
fc_j += 1
|
291
|
+
while fc_k < fc.length || bc_k < bc.length
|
292
|
+
if bc_k < bc.length && (!(fc_k < fc.length) || bc[bc_k][0] <= fc[fc_k][0])
|
293
|
+
if fc_j < bc[bc_k][1]
|
294
|
+
new_bc << bc[bc_k]
|
295
|
+
end
|
296
|
+
bc_k += 1
|
297
|
+
bc_j = bc_k < bc.length ? bc[bc_k][1] : 0
|
298
|
+
end
|
299
|
+
|
300
|
+
if fc_k < fc.length && (!(bc_k < bc.length) || fc[fc_k][0] < bc[bc_k][0])
|
301
|
+
if fc[fc_k][1] < bc_j
|
302
|
+
new_fc << fc[fc_k]
|
303
|
+
end
|
304
|
+
fc_j = fc[fc_k][1]
|
305
|
+
fc_k += 1
|
306
|
+
end
|
307
|
+
end
|
308
|
+
return new_fc, new_bc
|
309
|
+
end
|
310
|
+
|
311
|
+
class Closest
|
312
|
+
def initialize(arr)
|
313
|
+
@n = arr.length + 1
|
314
|
+
|
315
|
+
@table = Array.new
|
316
|
+
arr.each_index {|i|
|
317
|
+
s = arr[i]
|
318
|
+
@table[s] = [-1] unless @table[s]
|
319
|
+
@table[s] << i
|
320
|
+
}
|
321
|
+
@table.each_index {|s|
|
322
|
+
@table[s] = [-1] unless @table[s]
|
323
|
+
@table[s] << @n
|
324
|
+
}
|
325
|
+
end
|
326
|
+
|
327
|
+
def next(s, i)
|
328
|
+
t = @table[s]
|
329
|
+
|
330
|
+
if t.length < 10
|
331
|
+
t.each {|j| return j if i < j}
|
332
|
+
return @n
|
333
|
+
end
|
334
|
+
|
335
|
+
lower = -1
|
336
|
+
upper = t.length
|
337
|
+
while lower + 1 != upper
|
338
|
+
mid = (lower + upper) / 2
|
339
|
+
if t[mid] <= i
|
340
|
+
lower = mid
|
341
|
+
else
|
342
|
+
upper = mid
|
343
|
+
end
|
344
|
+
end
|
345
|
+
b = lower + 1
|
346
|
+
|
347
|
+
if b < t.length
|
348
|
+
return t[b]
|
349
|
+
else
|
350
|
+
return @n
|
351
|
+
end
|
352
|
+
|
353
|
+
end
|
354
|
+
|
355
|
+
def prev(s, i)
|
356
|
+
t = @table[s]
|
357
|
+
|
358
|
+
if t.length < 10
|
359
|
+
t.reverse_each {|j| return j if j < i}
|
360
|
+
return -1
|
361
|
+
end
|
362
|
+
|
363
|
+
lower = -1
|
364
|
+
upper = t.length
|
365
|
+
while lower + 1 != upper
|
366
|
+
mid = (lower + upper) / 2
|
367
|
+
if t[mid] < i
|
368
|
+
lower = mid
|
369
|
+
else
|
370
|
+
upper = mid
|
371
|
+
end
|
372
|
+
end
|
373
|
+
if 0 < upper
|
374
|
+
return t[upper - 1]
|
375
|
+
else
|
376
|
+
return -1
|
377
|
+
end
|
378
|
+
end
|
379
|
+
end
|
380
|
+
end
|
381
|
+
end
|
382
|
+
|