Project

General

Profile

1
class Diff
2

    
3
  VERSION = 0.3
4

    
5
  def Diff.lcs(a, b)
6
    astart = 0
7
    bstart = 0
8
    afinish = a.length-1
9
    bfinish = b.length-1
10
    mvector = []
11
    
12
    # First we prune off any common elements at the beginning
13
    while (astart <= afinish && bstart <= afinish && a[astart] == b[bstart])
14
      mvector[astart] = bstart
15
      astart += 1
16
      bstart += 1
17
    end
18
    
19
    # now the end
20
    while (astart <= afinish && bstart <= bfinish && a[afinish] == b[bfinish])
21
      mvector[afinish] = bfinish
22
      afinish -= 1
23
      bfinish -= 1
24
    end
25

    
26
    bmatches = b.reverse_hash(bstart..bfinish)
27
    thresh = []
28
    links = []
29
    
30
    (astart..afinish).each { |aindex|
31
      aelem = a[aindex]
32
      next unless bmatches.has_key? aelem
33
      k = nil
34
      bmatches[aelem].reverse.each { |bindex|
35
	if k && (thresh[k] > bindex) && (thresh[k-1] < bindex)
36
	  thresh[k] = bindex
37
	else
38
	  k = thresh.replacenextlarger(bindex, k)
39
	end
40
	links[k] = [ (k==0) ? nil : links[k-1], aindex, bindex ] if k
41
      }
42
    }
43

    
44
    if !thresh.empty?
45
      link = links[thresh.length-1]
46
      while link
47
	mvector[link[1]] = link[2]
48
	link = link[0]
49
      end
50
    end
51

    
52
    return mvector
53
  end
54

    
55
  def makediff(a, b)
56
    mvector = Diff.lcs(a, b)
57
    ai = bi = 0
58
    while ai < mvector.length
59
      bline = mvector[ai]
60
      if bline
61
	while bi < bline
62
	  discardb(bi, b[bi])
63
	  bi += 1
64
	end
65
	match(ai, bi)
66
	bi += 1
67
      else
68
	discarda(ai, a[ai])
69
      end
70
      ai += 1
71
    end
72
    while ai < a.length
73
      discarda(ai, a[ai])
74
      ai += 1
75
    end
76
    while bi < b.length
77
      discardb(bi, b[bi])
78
      bi += 1
79
    end
80
    match(ai, bi)
81
    1
82
  end
83

    
84
  def compactdiffs
85
    diffs = []
86
    @diffs.each { |df|
87
      i = 0
88
      curdiff = []
89
      while i < df.length
90
	whot = df[i][0]
91
	s = @isstring ? df[i][2].chr : [df[i][2]]
92
	p = df[i][1]
93
	last = df[i][1]
94
	i += 1
95
	while df[i] && df[i][0] == whot && df[i][1] == last+1
96
	  s << df[i][2]
97
	  last  = df[i][1]
98
	  i += 1
99
	end
100
	curdiff.push [whot, p, s]
101
      end
102
      diffs.push curdiff
103
    }
104
    return diffs
105
  end
106

    
107
  attr_reader :diffs, :difftype
108

    
109
  def initialize(diffs_or_a, b = nil, isstring = nil)
110
    if b.nil?
111
      @diffs = diffs_or_a
112
      @isstring = isstring
113
    else
114
      @diffs = []
115
      @curdiffs = []
116
      makediff(diffs_or_a, b)
117
      @difftype = diffs_or_a.class
118
    end
119
  end
120
  
121
  def match(ai, bi)
122
    @diffs.push @curdiffs unless @curdiffs.empty?
123
    @curdiffs = []
124
  end
125

    
126
  def discarda(i, elem)
127
    @curdiffs.push ['-', i, elem]
128
  end
129

    
130
  def discardb(i, elem)
131
    @curdiffs.push ['+', i, elem]
132
  end
133

    
134
  def compact
135
    return Diff.new(compactdiffs)
136
  end
137

    
138
  def compact!
139
    @diffs = compactdiffs
140
  end
141

    
142
  def inspect
143
    @diffs.inspect
144
  end
145

    
146
end
147

    
148
module Diffable
149
  def diff(b)
150
    Diff.new(self, b)
151
  end
152

    
153
  # Create a hash that maps elements of the array to arrays of indices
154
  # where the elements are found.
155

    
156
  def reverse_hash(range = (0...self.length))
157
    revmap = {}
158
    range.each { |i|
159
      elem = self[i]
160
      if revmap.has_key? elem
161
	revmap[elem].push i
162
      else
163
	revmap[elem] = [i]
164
      end
165
    }
166
    return revmap
167
  end
168

    
169
  def replacenextlarger(value, high = nil)
170
    high ||= self.length
171
    if self.empty? || value > self[-1]
172
      push value
173
      return high
174
    end
175
    # binary search for replacement point
176
    low = 0
177
    while low < high
178
      index = (high+low)/2
179
      found = self[index]
180
      return nil if value == found
181
      if value > found
182
	low = index + 1
183
      else
184
	high = index
185
      end
186
    end
187

    
188
    self[low] = value
189
    # $stderr << "replace #{value} : 0/#{low}/#{init_high} (#{steps} steps) (#{init_high-low} off )\n"
190
    # $stderr.puts self.inspect
191
    #gets
192
    #p length - low
193
    return low
194
  end
195

    
196
  def patch(diff)
197
    newary = nil
198
    if diff.difftype == String
199
      newary = diff.difftype.new('')
200
    else
201
      newary = diff.difftype.new
202
    end
203
    ai = 0
204
    bi = 0
205
    diff.diffs.each { |d|
206
      d.each { |mod|
207
	case mod[0]
208
	when '-'
209
	  while ai < mod[1]
210
	    newary << self[ai]
211
	    ai += 1
212
	    bi += 1
213
	  end
214
	  ai += 1
215
	when '+'
216
	  while bi < mod[1]
217
	    newary << self[ai]
218
	    ai += 1
219
	    bi += 1
220
	  end
221
	  newary << mod[2]
222
	  bi += 1
223
	else
224
	  raise "Unknown diff action"
225
	end
226
      }
227
    }
228
    while ai < self.length
229
      newary << self[ai]
230
      ai += 1
231
      bi += 1
232
    end
233
    return newary
234
  end
235
end
236

    
237
class Array
238
  include Diffable
239
end
240

    
241
class String
242
  include Diffable
243
end
244

    
245
=begin
246
= Diff
247
(({diff.rb})) - computes the differences between two arrays or
248
strings. Copyright (C) 2001 Lars Christensen
249

    
250
== Synopsis
251

    
252
    diff = Diff.new(a, b)
253
    b = a.patch(diff)
254

    
255
== Class Diff
256
=== Class Methods
257
--- Diff.new(a, b)
258
--- a.diff(b)
259
      Creates a Diff object which represent the differences between
260
      ((|a|)) and ((|b|)). ((|a|)) and ((|b|)) can be either be arrays
261
      of any objects, strings, or object of any class that include
262
      module ((|Diffable|))
263

    
264
== Module Diffable
265
The module ((|Diffable|)) is intended to be included in any class for
266
which differences are to be computed. Diffable is included into String
267
and Array when (({diff.rb})) is (({require}))'d.
268

    
269
Classes including Diffable should implement (({[]})) to get element at
270
integer indices, (({<<})) to append elements to the object and
271
(({ClassName#new})) should accept 0 arguments to create a new empty
272
object.
273

    
274
=== Instance Methods
275
--- Diffable#patch(diff)
276
      Applies the differences from ((|diff|)) to the object ((|obj|))
277
      and return the result. ((|obj|)) is not changed. ((|obj|)) and
278
      can be either an array or a string, but must match the object
279
      from which the ((|diff|)) was created.
280
=end
(2-2/5)