hashdiff 0.3.7 → 0.3.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.rubocop.yml +21 -0
- data/.travis.yml +2 -5
- data/Gemfile +1 -1
- data/README.md +1 -1
- data/Rakefile +7 -4
- data/changelog.md +4 -0
- data/hashdiff.gemspec +13 -11
- data/lib/hashdiff/diff.rb +56 -64
- data/lib/hashdiff/lcs.rb +23 -28
- data/lib/hashdiff/linear_compare_array.rb +4 -2
- data/lib/hashdiff/patch.rb +2 -4
- data/lib/hashdiff/util.rb +27 -31
- data/lib/hashdiff/version.rb +1 -1
- data/spec/hash_diff/best_diff_spec.rb +73 -0
- data/spec/hash_diff/diff_array_spec.rb +58 -0
- data/spec/hash_diff/diff_spec.rb +339 -0
- data/spec/hash_diff/lcs_spec.rb +74 -0
- data/spec/hash_diff/linear_compare_array_spec.rb +48 -0
- data/spec/hash_diff/patch_spec.rb +183 -0
- data/spec/hash_diff/util_spec.rb +85 -0
- metadata +55 -26
- data/spec/hashdiff/best_diff_spec.rb +0 -74
- data/spec/hashdiff/diff_array_spec.rb +0 -60
- data/spec/hashdiff/diff_spec.rb +0 -339
- data/spec/hashdiff/lcs_spec.rb +0 -75
- data/spec/hashdiff/linear_compare_array_spec.rb +0 -48
- data/spec/hashdiff/patch_spec.rb +0 -183
- data/spec/hashdiff/util_spec.rb +0 -78
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 482b0233e91f747074eada75539247148a14d31d
|
4
|
+
data.tar.gz: 0aba247c54a9e96d433d77ecc76b3e4cf1396075
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 10fe5b5b37c1a80d317d509b133a880211723ff4b184e892678eb7211de5bcf41b27d482151237052efabd6c9eee336507d5df4f19e2b0e727de970e47280cb4
|
7
|
+
data.tar.gz: 22baf87f78115a872a6df5600b8a31c2d8e3c35991ca114aff60a89af70d29b530f3771386d8bb67578324884f30291e120ed418f78c2768baa746e456f65d71
|
data/.rubocop.yml
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
require: rubocop-rspec
|
2
|
+
Metrics/PerceivedComplexity:
|
3
|
+
Enabled: false
|
4
|
+
Metrics/CyclomaticComplexity:
|
5
|
+
Enabled: false
|
6
|
+
Metrics/MethodLength:
|
7
|
+
Enabled: false
|
8
|
+
Metrics/AbcSize:
|
9
|
+
Enabled: false
|
10
|
+
Metrics/LineLength:
|
11
|
+
Enabled: false
|
12
|
+
Metrics/ClassLength:
|
13
|
+
Enabled: false
|
14
|
+
Metrics/BlockLength:
|
15
|
+
Enabled: false
|
16
|
+
Metrics/ModuleLength:
|
17
|
+
Enabled: false
|
18
|
+
Style/Documentation:
|
19
|
+
Enabled: false
|
20
|
+
RSpec/ExampleLength:
|
21
|
+
Enabled: false
|
data/.travis.yml
CHANGED
data/Gemfile
CHANGED
data/README.md
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
# HashDiff [](http://travis-ci.org/liufengyun/hashdiff) [](http://badge.fury.io/rb/hashdiff)
|
2
2
|
|
3
3
|
HashDiff is a ruby library to compute the smallest difference between two hashes.
|
4
4
|
|
data/Rakefile
CHANGED
@@ -1,13 +1,16 @@
|
|
1
|
-
|
1
|
+
$LOAD_PATH.push File.expand_path('lib', __dir__)
|
2
|
+
|
3
|
+
require 'rubocop/rake_task'
|
2
4
|
|
3
5
|
require 'bundler'
|
4
6
|
Bundler::GemHelper.install_tasks
|
5
7
|
|
6
8
|
require 'rspec/core/rake_task'
|
7
9
|
|
8
|
-
|
10
|
+
RuboCop::RakeTask.new
|
11
|
+
|
12
|
+
task default: %w[spec rubocop]
|
9
13
|
|
10
14
|
RSpec::Core::RakeTask.new(:spec) do |spec|
|
11
|
-
spec.pattern =
|
15
|
+
spec.pattern = './spec/**/*_spec.rb'
|
12
16
|
end
|
13
|
-
|
data/changelog.md
CHANGED
data/hashdiff.gemspec
CHANGED
@@ -1,25 +1,27 @@
|
|
1
|
-
$LOAD_PATH << File.expand_path(
|
1
|
+
$LOAD_PATH << File.expand_path('lib', __dir__)
|
2
2
|
require 'hashdiff/version'
|
3
3
|
|
4
4
|
Gem::Specification.new do |s|
|
5
|
-
s.name =
|
5
|
+
s.name = 'hashdiff'
|
6
6
|
s.version = HashDiff::VERSION
|
7
7
|
s.license = 'MIT'
|
8
|
-
s.summary =
|
9
|
-
s.description =
|
8
|
+
s.summary = ' HashDiff is a diff lib to compute the smallest difference between two hashes. '
|
9
|
+
s.description = ' HashDiff is a diff lib to compute the smallest difference between two hashes. '
|
10
10
|
|
11
11
|
s.files = `git ls-files`.split("\n")
|
12
12
|
s.test_files = `git ls-files -- Appraisals {spec}/*`.split("\n")
|
13
13
|
|
14
14
|
s.require_paths = ['lib']
|
15
|
-
s.required_ruby_version = Gem::Requirement.new(
|
15
|
+
s.required_ruby_version = Gem::Requirement.new('>= 1.9.3')
|
16
16
|
|
17
|
-
s.authors = [
|
18
|
-
s.email = [
|
17
|
+
s.authors = ['Liu Fengyun']
|
18
|
+
s.email = ['liufengyunchina@gmail.com']
|
19
19
|
|
20
|
-
s.homepage =
|
20
|
+
s.homepage = 'https://github.com/liufengyun/hashdiff'
|
21
21
|
|
22
|
-
s.add_development_dependency(
|
23
|
-
s.add_development_dependency(
|
24
|
-
s.add_development_dependency(
|
22
|
+
s.add_development_dependency('bluecloth')
|
23
|
+
s.add_development_dependency('rspec', '~> 2.0')
|
24
|
+
s.add_development_dependency('rubocop')
|
25
|
+
s.add_development_dependency('rubocop-rspec')
|
26
|
+
s.add_development_dependency('yard')
|
25
27
|
end
|
data/lib/hashdiff/diff.rb
CHANGED
@@ -1,5 +1,4 @@
|
|
1
1
|
module HashDiff
|
2
|
-
|
3
2
|
# Best diff two objects, which tries to generate the smallest change set using different similarity values.
|
4
3
|
#
|
5
4
|
# HashDiff.best_diff is useful in case of comparing two objects which include similar hashes in arrays.
|
@@ -29,20 +28,20 @@ module HashDiff
|
|
29
28
|
def self.best_diff(obj1, obj2, options = {}, &block)
|
30
29
|
options[:comparison] = block if block_given?
|
31
30
|
|
32
|
-
opts = { :
|
33
|
-
|
34
|
-
|
31
|
+
opts = { similarity: 0.3 }.merge!(options)
|
32
|
+
diffs1 = diff(obj1, obj2, opts)
|
33
|
+
count1 = count_diff diffs1
|
35
34
|
|
36
|
-
opts = { :
|
37
|
-
|
38
|
-
|
35
|
+
opts = { similarity: 0.5 }.merge!(options)
|
36
|
+
diffs2 = diff(obj1, obj2, opts)
|
37
|
+
count2 = count_diff diffs2
|
39
38
|
|
40
|
-
opts = { :
|
41
|
-
|
42
|
-
|
39
|
+
opts = { similarity: 0.8 }.merge!(options)
|
40
|
+
diffs3 = diff(obj1, obj2, opts)
|
41
|
+
count3 = count_diff diffs3
|
43
42
|
|
44
|
-
count, diffs =
|
45
|
-
|
43
|
+
count, diffs = count1 < count2 ? [count1, diffs1] : [count2, diffs2]
|
44
|
+
count < count3 ? diffs : diffs3
|
46
45
|
end
|
47
46
|
|
48
47
|
# Compute the diff of two hashes or arrays
|
@@ -74,14 +73,14 @@ module HashDiff
|
|
74
73
|
# @since 0.0.1
|
75
74
|
def self.diff(obj1, obj2, options = {}, &block)
|
76
75
|
opts = {
|
77
|
-
:
|
78
|
-
:
|
79
|
-
:
|
80
|
-
:
|
81
|
-
:
|
82
|
-
:
|
83
|
-
:
|
84
|
-
:
|
76
|
+
prefix: '',
|
77
|
+
similarity: 0.8,
|
78
|
+
delimiter: '.',
|
79
|
+
strict: true,
|
80
|
+
strip: false,
|
81
|
+
numeric_tolerance: 0,
|
82
|
+
array_path: false,
|
83
|
+
use_lcs: true
|
85
84
|
}.merge!(options)
|
86
85
|
|
87
86
|
opts[:prefix] = [] if opts[:array_path] && opts[:prefix] == ''
|
@@ -92,21 +91,13 @@ module HashDiff
|
|
92
91
|
result = custom_compare(opts[:comparison], opts[:prefix], obj1, obj2)
|
93
92
|
return result if result
|
94
93
|
|
95
|
-
if obj1.nil?
|
96
|
-
return []
|
97
|
-
end
|
94
|
+
return [] if obj1.nil? && obj2.nil?
|
98
95
|
|
99
|
-
if obj1.nil?
|
100
|
-
return [['~', opts[:prefix], nil, obj2]]
|
101
|
-
end
|
96
|
+
return [['~', opts[:prefix], nil, obj2]] if obj1.nil?
|
102
97
|
|
103
|
-
if obj2.nil?
|
104
|
-
return [['~', opts[:prefix], obj1, nil]]
|
105
|
-
end
|
98
|
+
return [['~', opts[:prefix], obj1, nil]] if obj2.nil?
|
106
99
|
|
107
|
-
unless comparable?(obj1, obj2, opts[:strict])
|
108
|
-
return [['~', opts[:prefix], obj1, obj2]]
|
109
|
-
end
|
100
|
+
return [['~', opts[:prefix], obj1, obj2]] unless comparable?(obj1, obj2, opts[:strict])
|
110
101
|
|
111
102
|
result = []
|
112
103
|
if obj1.is_a?(Array) && opts[:use_lcs]
|
@@ -114,7 +105,7 @@ module HashDiff
|
|
114
105
|
# use a's index for similarity
|
115
106
|
lcs.each do |pair|
|
116
107
|
prefix = prefix_append_array_index(opts[:prefix], pair[0], opts)
|
117
|
-
result.concat(diff(obj1[pair[0]], obj2[pair[1]], opts.merge(:
|
108
|
+
result.concat(diff(obj1[pair[0]], obj2[pair[1]], opts.merge(prefix: prefix)))
|
118
109
|
end
|
119
110
|
end
|
120
111
|
|
@@ -135,7 +126,7 @@ module HashDiff
|
|
135
126
|
added_keys = obj2.keys - obj1.keys
|
136
127
|
|
137
128
|
# add deleted properties
|
138
|
-
deleted_keys.sort_by{|k,
|
129
|
+
deleted_keys.sort_by { |k, _v| k.to_s }.each do |k|
|
139
130
|
change_key = prefix_append_key(opts[:prefix], k, opts)
|
140
131
|
custom_result = custom_compare(opts[:comparison], change_key, obj1[k], nil)
|
141
132
|
|
@@ -147,26 +138,27 @@ module HashDiff
|
|
147
138
|
end
|
148
139
|
|
149
140
|
# recursive comparison for common keys
|
150
|
-
common_keys.sort_by{|k,
|
141
|
+
common_keys.sort_by { |k, _v| k.to_s }.each do |k|
|
151
142
|
prefix = prefix_append_key(opts[:prefix], k, opts)
|
152
|
-
result.concat(diff(obj1[k], obj2[k], opts.merge(:
|
143
|
+
result.concat(diff(obj1[k], obj2[k], opts.merge(prefix: prefix)))
|
153
144
|
end
|
154
145
|
|
155
146
|
# added properties
|
156
|
-
added_keys.sort_by{|k,
|
147
|
+
added_keys.sort_by { |k, _v| k.to_s }.each do |k|
|
157
148
|
change_key = prefix_append_key(opts[:prefix], k, opts)
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
149
|
+
next if obj1.key?(k)
|
150
|
+
|
151
|
+
custom_result = custom_compare(opts[:comparison], change_key, nil, obj2[k])
|
152
|
+
|
153
|
+
if custom_result
|
154
|
+
result.concat(custom_result)
|
155
|
+
else
|
156
|
+
result << ['+', change_key, obj2[k]]
|
166
157
|
end
|
167
158
|
end
|
168
159
|
else
|
169
160
|
return [] if compare_values(obj1, obj2, opts)
|
161
|
+
|
170
162
|
return [['~', opts[:prefix], obj1, obj2]]
|
171
163
|
end
|
172
164
|
|
@@ -176,36 +168,36 @@ module HashDiff
|
|
176
168
|
# @private
|
177
169
|
#
|
178
170
|
# diff array using LCS algorithm
|
179
|
-
def self.diff_array_lcs(
|
171
|
+
def self.diff_array_lcs(arraya, arrayb, options = {})
|
180
172
|
opts = {
|
181
|
-
:
|
182
|
-
:
|
183
|
-
:
|
173
|
+
prefix: '',
|
174
|
+
similarity: 0.8,
|
175
|
+
delimiter: '.'
|
184
176
|
}.merge!(options)
|
185
177
|
|
186
178
|
change_set = []
|
187
|
-
if
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
change_set << ['+', index,
|
179
|
+
return [] if arraya.empty? && arrayb.empty?
|
180
|
+
|
181
|
+
if arraya.empty?
|
182
|
+
arrayb.each_index do |index|
|
183
|
+
change_set << ['+', index, arrayb[index]]
|
192
184
|
end
|
193
185
|
return change_set
|
194
|
-
elsif
|
195
|
-
|
196
|
-
i =
|
197
|
-
change_set << ['-', i,
|
186
|
+
elsif arrayb.empty?
|
187
|
+
arraya.each_index do |index|
|
188
|
+
i = arraya.size - index - 1
|
189
|
+
change_set << ['-', i, arraya[i]]
|
198
190
|
end
|
199
191
|
return change_set
|
200
192
|
end
|
201
193
|
|
202
|
-
links = lcs(
|
194
|
+
links = lcs(arraya, arrayb, opts)
|
203
195
|
|
204
196
|
# yield common
|
205
197
|
yield links if block_given?
|
206
198
|
|
207
199
|
# padding the end
|
208
|
-
links << [
|
200
|
+
links << [arraya.size, arrayb.size]
|
209
201
|
|
210
202
|
last_x = -1
|
211
203
|
last_y = -1
|
@@ -213,13 +205,13 @@ module HashDiff
|
|
213
205
|
x, y = pair
|
214
206
|
|
215
207
|
# remove from a, beginning from the end
|
216
|
-
(x > last_x + 1)
|
217
|
-
change_set << ['-', last_y + i + 1,
|
208
|
+
(x > last_x + 1) && (x - last_x - 2).downto(0).each do |i|
|
209
|
+
change_set << ['-', last_y + i + 1, arraya[i + last_x + 1]]
|
218
210
|
end
|
219
211
|
|
220
212
|
# add from b, beginning from the head
|
221
|
-
(y > last_y + 1)
|
222
|
-
change_set << ['+', last_y + i + 1,
|
213
|
+
(y > last_y + 1) && 0.upto(y - last_y - 2).each do |i|
|
214
|
+
change_set << ['+', last_y + i + 1, arrayb[i + last_y + 1]]
|
223
215
|
end
|
224
216
|
|
225
217
|
# update flags
|
data/lib/hashdiff/lcs.rb
CHANGED
@@ -3,44 +3,40 @@ module HashDiff
|
|
3
3
|
#
|
4
4
|
# caculate array difference using LCS algorithm
|
5
5
|
# http://en.wikipedia.org/wiki/Longest_common_subsequence_problem
|
6
|
-
def self.lcs(
|
7
|
-
opts = { :
|
6
|
+
def self.lcs(arraya, arrayb, options = {})
|
7
|
+
opts = { similarity: 0.8 }.merge!(options)
|
8
8
|
|
9
9
|
opts[:prefix] = prefix_append_array_index(opts[:prefix], '*', opts)
|
10
10
|
|
11
|
-
return [] if
|
11
|
+
return [] if arraya.empty? || arrayb.empty?
|
12
12
|
|
13
13
|
a_start = b_start = 0
|
14
|
-
a_finish =
|
15
|
-
b_finish =
|
14
|
+
a_finish = arraya.size - 1
|
15
|
+
b_finish = arrayb.size - 1
|
16
16
|
vector = []
|
17
17
|
|
18
18
|
lcs = []
|
19
19
|
(b_start..b_finish).each do |bi|
|
20
|
-
lcs[bi] = []
|
20
|
+
lcs[bi] = []
|
21
21
|
(a_start..a_finish).each do |ai|
|
22
|
-
if similar?(
|
23
|
-
topleft = (ai > 0
|
22
|
+
if similar?(arraya[ai], arrayb[bi], opts)
|
23
|
+
topleft = (ai > 0) && (bi > 0) ? lcs[bi - 1][ai - 1][1] : 0
|
24
24
|
lcs[bi][ai] = [:topleft, topleft + 1]
|
25
|
-
elsif
|
26
|
-
|
27
|
-
|
28
|
-
count = (top > left) ? top : left
|
25
|
+
elsif (top = bi > 0 ? lcs[bi - 1][ai][1] : 0)
|
26
|
+
left = ai > 0 ? lcs[bi][ai - 1][1] : 0
|
27
|
+
count = top > left ? top : left
|
29
28
|
|
30
|
-
direction =
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
direction = :both
|
42
|
-
end
|
43
|
-
end
|
29
|
+
direction = if top > left
|
30
|
+
:top
|
31
|
+
elsif top < left
|
32
|
+
:left
|
33
|
+
elsif bi.zero?
|
34
|
+
:top
|
35
|
+
elsif ai.zero?
|
36
|
+
:left
|
37
|
+
else
|
38
|
+
:both
|
39
|
+
end
|
44
40
|
|
45
41
|
lcs[bi][ai] = [direction, count]
|
46
42
|
end
|
@@ -49,7 +45,7 @@ module HashDiff
|
|
49
45
|
|
50
46
|
x = a_finish
|
51
47
|
y = b_finish
|
52
|
-
while x >= 0
|
48
|
+
while (x >= 0) && (y >= 0) && (lcs[y][x][1] > 0)
|
53
49
|
if lcs[y][x][0] == :both
|
54
50
|
x -= 1
|
55
51
|
elsif lcs[y][x][0] == :topleft
|
@@ -65,5 +61,4 @@ module HashDiff
|
|
65
61
|
|
66
62
|
vector
|
67
63
|
end
|
68
|
-
|
69
64
|
end
|
@@ -5,7 +5,7 @@ module HashDiff
|
|
5
5
|
# than using the lcs algorithm but is considerably faster
|
6
6
|
class LinearCompareArray
|
7
7
|
def self.call(old_array, new_array, options = {})
|
8
|
-
instance =
|
8
|
+
instance = new(old_array, new_array, options)
|
9
9
|
instance.call
|
10
10
|
end
|
11
11
|
|
@@ -79,7 +79,7 @@ module HashDiff
|
|
79
79
|
|
80
80
|
def item_difference(old_item, new_item, item_index)
|
81
81
|
prefix = HashDiff.prefix_append_array_index(options[:prefix], item_index, options)
|
82
|
-
HashDiff.diff(old_item, new_item, options.merge(:
|
82
|
+
HashDiff.diff(old_item, new_item, options.merge(prefix: prefix))
|
83
83
|
end
|
84
84
|
|
85
85
|
# look ahead in the new array to see if the current item appears later
|
@@ -120,6 +120,7 @@ module HashDiff
|
|
120
120
|
|
121
121
|
def append_addititions_before_match(match_index)
|
122
122
|
return unless match_index
|
123
|
+
|
123
124
|
(new_index...match_index).each { |i| append_addition(new_array[i], i) }
|
124
125
|
self.expected_additions = expected_additions - (match_index - new_index)
|
125
126
|
self.new_index = match_index
|
@@ -127,6 +128,7 @@ module HashDiff
|
|
127
128
|
|
128
129
|
def append_deletions_before_match(match_index)
|
129
130
|
return unless match_index
|
131
|
+
|
130
132
|
(old_index...match_index).each { |i| append_deletion(old_array[i], i) }
|
131
133
|
self.expected_additions = expected_additions + (match_index - new_index)
|
132
134
|
self.old_index = match_index
|