hashdiff 0.3.5 → 0.3.6

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 4ba86448df8842fe6323572feb2c89110954b775
4
- data.tar.gz: e3d610f32c07e65e06f825e107371caaaf2e5896
3
+ metadata.gz: aa9fedd172b3d7020ea1078bc10fc290bf77381c
4
+ data.tar.gz: cf7ad118f84f90035283165ce4bc8c473f642ccc
5
5
  SHA512:
6
- metadata.gz: 7e7b28d277160a603df99c83022f6b8bd7bb27ac5d467e351e5834ba955195ed493249b7a7e213dafe0cb1aa630c5de46fe11a95b5942de4ba9c1f4e66262ec3
7
- data.tar.gz: d5290cbc0b5c97038cce213e627fc20fcaf4e7405888c217c78b949c8fc98990ab6d3ae18b0f91d2909c069ffccc20a9d0d45aa572419252aa6eae960df6491d
6
+ metadata.gz: 970a50d8a7cd540d7bb8bd91736f2030fb48b2bacfe505621fc0e9fda93e5835bab16fcc0e40310c5b62774121bd80b279c52b97a2af024e46b63f8b1bb02114
7
+ data.tar.gz: 96de7b52391861d461a663ce2a7e12220558a65e6f6a001a68a54763a336bc5795fa5307ed3b41e84026060a5c80681b6914538a60112a305d100636589d2998
data/Gemfile CHANGED
@@ -3,5 +3,4 @@ gemspec
3
3
 
4
4
  group :test do
5
5
  gem 'rake', '< 11'
6
- gem 'codecov'
7
6
  end
@@ -1,5 +1,9 @@
1
1
  # Change Log
2
2
 
3
+ ## v0.3.6 2017-08-22
4
+
5
+ * add option `use_lcs` #35
6
+
3
7
  ## v0.3.5 2017-08-06
4
8
 
5
9
  * add option `array_path` #34
@@ -1,5 +1,6 @@
1
1
  require 'hashdiff/util'
2
2
  require 'hashdiff/lcs'
3
+ require 'hashdiff/linear_compare_array'
3
4
  require 'hashdiff/diff'
4
5
  require 'hashdiff/patch'
5
6
  require 'hashdiff/version'
@@ -77,7 +77,8 @@ module HashDiff
77
77
  :strict => true,
78
78
  :strip => false,
79
79
  :numeric_tolerance => 0,
80
- :array_path => false
80
+ :array_path => false,
81
+ :use_lcs => true
81
82
  }.merge!(options)
82
83
 
83
84
  opts[:prefix] = [] if opts[:array_path] && opts[:prefix] == ''
@@ -105,8 +106,8 @@ module HashDiff
105
106
  end
106
107
 
107
108
  result = []
108
- if obj1.is_a?(Array)
109
- changeset = diff_array(obj1, obj2, opts) do |lcs|
109
+ if obj1.is_a?(Array) && opts[:use_lcs]
110
+ changeset = diff_array_lcs(obj1, obj2, opts) do |lcs|
110
111
  # use a's index for similarity
111
112
  lcs.each do |pair|
112
113
  prefix = prefix_append_array_index(opts[:prefix], pair[0], opts)
@@ -122,6 +123,8 @@ module HashDiff
122
123
  result << ['+', change_key, change[2]]
123
124
  end
124
125
  end
126
+ elsif obj1.is_a?(Array) && !opts[:use_lcs]
127
+ result.concat(LinearCompareArray.call(obj1, obj2, opts))
125
128
  elsif obj1.is_a?(Hash)
126
129
 
127
130
  deleted_keys = obj1.keys - obj2.keys
@@ -170,7 +173,7 @@ module HashDiff
170
173
  # @private
171
174
  #
172
175
  # diff array using LCS algorithm
173
- def self.diff_array(a, b, options = {})
176
+ def self.diff_array_lcs(a, b, options = {})
174
177
  opts = {
175
178
  :prefix => '',
176
179
  :similarity => 0.8,
@@ -223,5 +226,4 @@ module HashDiff
223
226
 
224
227
  change_set
225
228
  end
226
-
227
229
  end
@@ -0,0 +1,155 @@
1
+ module HashDiff
2
+ # @private
3
+ #
4
+ # Used to compare arrays in a linear complexity, which produces longer diffs
5
+ # than using the lcs algorithm but is considerably faster
6
+ class LinearCompareArray
7
+ def self.call(old_array, new_array, options = {})
8
+ instance = self.new(old_array, new_array, options)
9
+ instance.call
10
+ end
11
+
12
+ def call
13
+ return [] if old_array.empty? && new_array.empty?
14
+
15
+ self.old_index = 0
16
+ self.new_index = 0
17
+ # by comparing the array lengths we can expect that a number of items
18
+ # are either added or removed
19
+ self.expected_additions = new_array.length - old_array.length
20
+
21
+ loop do
22
+ if extra_items_in_old_array?
23
+ append_deletion(old_array[old_index], old_index)
24
+ elsif extra_items_in_new_array?
25
+ append_addition(new_array[new_index], new_index)
26
+ else
27
+ compare_at_index
28
+ end
29
+
30
+ self.old_index = old_index + 1
31
+ self.new_index = new_index + 1
32
+ break if iterated_through_both_arrays?
33
+ end
34
+
35
+ changes
36
+ end
37
+
38
+ private
39
+
40
+ attr_reader :old_array, :new_array, :options, :additions, :deletions, :differences
41
+ attr_accessor :old_index, :new_index, :expected_additions
42
+
43
+ def initialize(old_array, new_array, options)
44
+ @old_array = old_array
45
+ @new_array = new_array
46
+ @options = { prefix: '' }.merge!(options)
47
+
48
+ @additions = []
49
+ @deletions = []
50
+ @differences = []
51
+ end
52
+
53
+ def extra_items_in_old_array?
54
+ old_index < old_array.length && new_index >= new_array.length
55
+ end
56
+
57
+ def extra_items_in_new_array?
58
+ new_index < new_array.length && old_index >= old_array.length
59
+ end
60
+
61
+ def iterated_through_both_arrays?
62
+ old_index >= old_array.length && new_index >= new_array.length
63
+ end
64
+
65
+ def compare_at_index
66
+ difference = item_difference(old_array[old_index], new_array[new_index], old_index)
67
+ return if difference.empty?
68
+
69
+ index_after_additions = index_of_match_after_additions
70
+ append_addititions_before_match(index_after_additions)
71
+
72
+ index_after_deletions = index_of_match_after_deletions
73
+ append_deletions_before_match(index_after_deletions)
74
+
75
+ match = index_after_additions || index_after_deletions
76
+
77
+ append_differences(difference) unless match
78
+ end
79
+
80
+ def item_difference(old_item, new_item, item_index)
81
+ prefix = HashDiff.prefix_append_array_index(options[:prefix], item_index, options)
82
+ HashDiff.diff(old_item, new_item, options.merge(:prefix => prefix))
83
+ end
84
+
85
+ # look ahead in the new array to see if the current item appears later
86
+ # thereby having new items added
87
+ def index_of_match_after_additions
88
+ return unless expected_additions > 0
89
+
90
+ (1..expected_additions).each do |i|
91
+ next_difference = item_difference(
92
+ old_array[old_index],
93
+ new_array[new_index + i],
94
+ old_index
95
+ )
96
+
97
+ return new_index + i if next_difference.empty?
98
+ end
99
+
100
+ nil
101
+ end
102
+
103
+ # look ahead in the old array to see if the current item appears later
104
+ # thereby having items removed
105
+ def index_of_match_after_deletions
106
+ return unless expected_additions < 0
107
+
108
+ (1..(expected_additions.abs)).each do |i|
109
+ next_difference = item_difference(
110
+ old_array[old_index + i],
111
+ new_array[new_index],
112
+ old_index
113
+ )
114
+
115
+ return old_index + i if next_difference.empty?
116
+ end
117
+
118
+ nil
119
+ end
120
+
121
+ def append_addititions_before_match(match_index)
122
+ return unless match_index
123
+ (new_index...match_index).each { |i| append_addition(new_array[i], i) }
124
+ self.expected_additions = expected_additions - (match_index - new_index)
125
+ self.new_index = match_index
126
+ end
127
+
128
+ def append_deletions_before_match(match_index)
129
+ return unless match_index
130
+ (old_index...match_index).each { |i| append_deletion(old_array[i], i) }
131
+ self.expected_additions = expected_additions + (match_index - new_index)
132
+ self.old_index = match_index
133
+ end
134
+
135
+ def append_addition(item, index)
136
+ key = HashDiff.prefix_append_array_index(options[:prefix], index, options)
137
+ additions << ['+', key, item]
138
+ end
139
+
140
+ def append_deletion(item, index)
141
+ key = HashDiff.prefix_append_array_index(options[:prefix], index, options)
142
+ deletions << ['-', key, item]
143
+ end
144
+
145
+ def append_differences(difference)
146
+ differences.concat(difference)
147
+ end
148
+
149
+ def changes
150
+ # this algorithm only allows there to be additions or deletions
151
+ # deletions are reverse so they don't change the index of earlier items
152
+ differences + additions + deletions.reverse
153
+ end
154
+ end
155
+ end
@@ -1,3 +1,3 @@
1
1
  module HashDiff
2
- VERSION = '0.3.5'
2
+ VERSION = '0.3.6'
3
3
  end
@@ -5,7 +5,7 @@ describe HashDiff do
5
5
  a = [1, 2, 3]
6
6
  b = [1, 2, 3]
7
7
 
8
- diff = HashDiff.diff_array(a, b)
8
+ diff = HashDiff.diff_array_lcs(a, b)
9
9
  diff.should == []
10
10
  end
11
11
 
@@ -13,7 +13,7 @@ describe HashDiff do
13
13
  a = [1, 2, 3]
14
14
  b = [1, 8, 7]
15
15
 
16
- diff = HashDiff.diff_array(a, b)
16
+ diff = HashDiff.diff_array_lcs(a, b)
17
17
  diff.should == [['-', 2, 3], ['-', 1, 2], ['+', 1, 8], ['+', 2, 7]]
18
18
  end
19
19
 
@@ -21,7 +21,7 @@ describe HashDiff do
21
21
  a = [1, 2]
22
22
  b = []
23
23
 
24
- diff = HashDiff.diff_array(a, b)
24
+ diff = HashDiff.diff_array_lcs(a, b)
25
25
  diff.should == [['-', 1, 2], ['-', 0, 1]]
26
26
  end
27
27
 
@@ -29,7 +29,7 @@ describe HashDiff do
29
29
  a = []
30
30
  b = [1, 2]
31
31
 
32
- diff = HashDiff.diff_array(a, b)
32
+ diff = HashDiff.diff_array_lcs(a, b)
33
33
  diff.should == [['+', 0, 1], ['+', 1, 2]]
34
34
  end
35
35
 
@@ -37,7 +37,7 @@ describe HashDiff do
37
37
  a = [1, 3, 5, 7]
38
38
  b = [2, 3, 7, 5]
39
39
 
40
- diff = HashDiff.diff_array(a, b)
40
+ diff = HashDiff.diff_array_lcs(a, b)
41
41
  diff.should == [['-', 0, 1], ['+', 0, 2], ['+', 2, 7], ['-', 4, 7]]
42
42
  end
43
43
 
@@ -45,14 +45,14 @@ describe HashDiff do
45
45
  a = [1, 3, 4, 7]
46
46
  b = [2, 3, 7, 5]
47
47
 
48
- diff = HashDiff.diff_array(a, b)
48
+ diff = HashDiff.diff_array_lcs(a, b)
49
49
  diff.should == [['-', 0, 1], ['+', 0, 2], ['-', 2, 4], ['+', 3, 5]]
50
50
  end
51
51
 
52
52
  it "should be able to diff two arrays with similar elements" do
53
53
  a = [{'a' => 1, 'b' => 2, 'c' => 3, 'd' => 4, 'e' => 5}, 3]
54
54
  b = [1, {'a' => 1, 'b' => 2, 'c' => 3, 'e' => 5}]
55
- diff = HashDiff.diff_array(a, b)
55
+ diff = HashDiff.diff_array_lcs(a, b)
56
56
  diff.should == [['+', 0, 1], ['-', 2, 3]]
57
57
  end
58
58
 
@@ -310,4 +310,30 @@ describe HashDiff do
310
310
  diff.should == [['-', [time], 'foo'], ['+', [0], 'bar']]
311
311
  end
312
312
  end
313
+
314
+ context 'when :use_lcs is false' do
315
+ it 'should show items in an array as changed' do
316
+ x = [:a, :b]
317
+ y = [:c, :d]
318
+ diff = HashDiff.diff(x, y, :use_lcs => false)
319
+
320
+ diff.should == [['~', '[0]', :a, :c], ['~', '[1]', :b, :d]]
321
+ end
322
+
323
+ it 'should show additions to arrays' do
324
+ x = { :a => [0] }
325
+ y = { :a => [0, 1] }
326
+ diff = HashDiff.diff(x, y, :use_lcs => false)
327
+
328
+ diff.should == [['+', 'a[1]', 1]]
329
+ end
330
+
331
+ it 'shows changes to nested arrays' do
332
+ x = { :a => [[0, 1]] }
333
+ y = { :a => [[1, 2]] }
334
+ diff = HashDiff.diff(x, y, :use_lcs => false)
335
+
336
+ diff.should == [['~', 'a[0][0]', 0, 1], ['~', 'a[0][1]', 1, 2]]
337
+ end
338
+ end
313
339
  end
@@ -0,0 +1,48 @@
1
+ require 'spec_helper'
2
+
3
+ describe HashDiff::LinearCompareArray do
4
+ it "should find no differences between two empty arrays" do
5
+ difference = described_class.call([], [])
6
+ difference.should == []
7
+ end
8
+
9
+ it "should find added items when the old array is empty" do
10
+ difference = described_class.call([], [:a, :b])
11
+ difference.should == [['+', '[0]', :a], ['+', '[1]', :b]]
12
+ end
13
+
14
+ it "should find removed items when the new array is empty" do
15
+ difference = described_class.call([:a, :b], [])
16
+ difference.should == [['-', '[1]', :b], ['-', '[0]', :a]]
17
+ end
18
+
19
+ it "should find no differences between identical arrays" do
20
+ difference = described_class.call([:a, :b], [:a, :b])
21
+ difference.should == []
22
+ end
23
+
24
+ it "should find added items in an array" do
25
+ difference = described_class.call([:a, :d], [:a, :b, :c, :d])
26
+ difference.should == [['+', '[1]', :b], ['+', '[2]', :c]]
27
+ end
28
+
29
+ it "should find removed items in an array" do
30
+ difference = described_class.call([:a, :b, :c, :d, :e, :f], [:a, :d, :f])
31
+ difference.should == [['-', '[4]', :e], ['-', '[2]', :c], ['-', '[1]', :b]]
32
+ end
33
+
34
+ it "should show additions and deletions as changed items" do
35
+ difference = described_class.call([:a, :b, :c], [:c, :b, :a])
36
+ difference.should == [['~', '[0]', :a, :c], ['~', '[2]', :c, :a]]
37
+ end
38
+
39
+ it "should show changed items in a hash" do
40
+ difference = described_class.call([{ :a => :b }], [{ :a => :c }])
41
+ difference.should == [['~', '[0].a', :b, :c]]
42
+ end
43
+
44
+ it "should show changed items and added items" do
45
+ difference = described_class.call([{ :a => 1, :b => 2 }], [{ :a => 2, :b => 2 }, :item])
46
+ difference.should == [['~', '[0].a', 1, 2], ['+', '[1]', :item]]
47
+ end
48
+ end
@@ -1,10 +1,3 @@
1
- require 'simplecov'
2
- SimpleCov.start
3
- if ENV['CI'] == 'true'
4
- require 'codecov'
5
- SimpleCov.formatter = SimpleCov::Formatter::Codecov
6
- end
7
-
8
1
  $LOAD_PATH << File.join(File.dirname(__FILE__), '..', 'lib')
9
2
 
10
3
  require 'rubygems'
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: hashdiff
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.5
4
+ version: 0.3.6
5
5
  platform: ruby
6
6
  authors:
7
7
  - Liu Fengyun
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2017-08-06 00:00:00.000000000 Z
11
+ date: 2017-08-22 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rspec
@@ -73,6 +73,7 @@ files:
73
73
  - lib/hashdiff.rb
74
74
  - lib/hashdiff/diff.rb
75
75
  - lib/hashdiff/lcs.rb
76
+ - lib/hashdiff/linear_compare_array.rb
76
77
  - lib/hashdiff/patch.rb
77
78
  - lib/hashdiff/util.rb
78
79
  - lib/hashdiff/version.rb
@@ -80,6 +81,7 @@ files:
80
81
  - spec/hashdiff/diff_array_spec.rb
81
82
  - spec/hashdiff/diff_spec.rb
82
83
  - spec/hashdiff/lcs_spec.rb
84
+ - spec/hashdiff/linear_compare_array_spec.rb
83
85
  - spec/hashdiff/patch_spec.rb
84
86
  - spec/hashdiff/util_spec.rb
85
87
  - spec/spec_helper.rb