hashdiff 0.3.8 → 0.3.9
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 +3 -0
- data/Gemfile +2 -0
- data/README.md +3 -1
- data/Rakefile +2 -0
- data/changelog.md +4 -1
- data/hashdiff.gemspec +3 -1
- data/lib/hashdiff.rb +8 -6
- data/lib/hashdiff/diff.rb +22 -13
- data/lib/hashdiff/lcs.rb +4 -2
- data/lib/hashdiff/linear_compare_array.rb +2 -0
- data/lib/hashdiff/patch.rb +2 -0
- data/lib/hashdiff/util.rb +9 -8
- data/lib/hashdiff/version.rb +3 -1
- data/spec/hash_diff/best_diff_spec.rb +2 -0
- data/spec/hash_diff/diff_array_spec.rb +2 -0
- data/spec/hash_diff/diff_spec.rb +2 -0
- data/spec/hash_diff/lcs_spec.rb +2 -0
- data/spec/hash_diff/linear_compare_array_spec.rb +2 -0
- data/spec/hash_diff/patch_spec.rb +2 -0
- data/spec/hash_diff/util_spec.rb +10 -0
- data/spec/spec_helper.rb +2 -0
- metadata +3 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 1de380f239260b12890d36de1548ee8a9159a15d
|
4
|
+
data.tar.gz: be13f7000b31ac211e59c8e5c924a976a39fb26c
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: bad33e3fd08a4f7b14388f54c17060153a48832eda6c06e6d137ce23b901cd14183630eabcfa7e1da6d2c93f33d7b59109a7fee0e78a23f2a3102e3576c35846
|
7
|
+
data.tar.gz: 7fa406149636c91d9cc5abc49b926f92e8b4962654e9b78ef18b3938dc971ce2f51cf16b2c01b3968556cc20fbfb577437b76a7094ecf0ba4eb5cd00cdba0bb3
|
data/.rubocop.yml
CHANGED
data/Gemfile
CHANGED
data/README.md
CHANGED
@@ -8,6 +8,9 @@ HashDiff does not monkey-patch any existing class. All features are contained in
|
|
8
8
|
|
9
9
|
**Docs**: [Documentation](http://rubydoc.info/gems/hashdiff)
|
10
10
|
|
11
|
+
|
12
|
+
__WARNING__: Don't use the library for comparing large arrays, say ~10K (see #49).
|
13
|
+
|
11
14
|
## Why HashDiff?
|
12
15
|
|
13
16
|
Given two Hashes A and B, sometimes you face the question: what's the smallest modification that can be made to change A into B?
|
@@ -262,4 +265,3 @@ HashDiff.diff(a, b) => []
|
|
262
265
|
## License
|
263
266
|
|
264
267
|
HashDiff is distributed under the MIT-LICENSE.
|
265
|
-
|
data/Rakefile
CHANGED
data/changelog.md
CHANGED
@@ -1,5 +1,9 @@
|
|
1
1
|
# Change Log
|
2
2
|
|
3
|
+
## v0.3.9 2019-04-22
|
4
|
+
|
5
|
+
* Performance tweak (thanks @krzysiek1507: #51 #52 #53)
|
6
|
+
|
3
7
|
## v0.3.8 2018-12-30
|
4
8
|
|
5
9
|
* Add Rubocop and drops Ruby 1.9 support #47
|
@@ -73,4 +77,3 @@ For example, `diff({a:2, c:[4, 5]}, {a:2}) will generate following output:
|
|
73
77
|
instead of following:
|
74
78
|
|
75
79
|
[['-', 'c[0]', 4], ['-', 'c[1]', 5], ['-', 'c', []]]
|
76
|
-
|
data/hashdiff.gemspec
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
$LOAD_PATH << File.expand_path('lib', __dir__)
|
2
4
|
require 'hashdiff/version'
|
3
5
|
|
@@ -12,7 +14,7 @@ Gem::Specification.new do |s|
|
|
12
14
|
s.test_files = `git ls-files -- Appraisals {spec}/*`.split("\n")
|
13
15
|
|
14
16
|
s.require_paths = ['lib']
|
15
|
-
s.required_ruby_version = Gem::Requirement.new('>=
|
17
|
+
s.required_ruby_version = Gem::Requirement.new('>= 2.0.0')
|
16
18
|
|
17
19
|
s.authors = ['Liu Fengyun']
|
18
20
|
s.email = ['liufengyunchina@gmail.com']
|
data/lib/hashdiff.rb
CHANGED
@@ -1,6 +1,8 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'hashdiff/util'
|
4
|
+
require_relative 'hashdiff/lcs'
|
5
|
+
require_relative 'hashdiff/linear_compare_array'
|
6
|
+
require_relative 'hashdiff/diff'
|
7
|
+
require_relative 'hashdiff/patch'
|
8
|
+
require_relative 'hashdiff/version'
|
data/lib/hashdiff/diff.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module HashDiff
|
2
4
|
# Best diff two objects, which tries to generate the smallest change set using different similarity values.
|
3
5
|
#
|
@@ -120,13 +122,15 @@ module HashDiff
|
|
120
122
|
elsif obj1.is_a?(Array) && !opts[:use_lcs]
|
121
123
|
result.concat(LinearCompareArray.call(obj1, obj2, opts))
|
122
124
|
elsif obj1.is_a?(Hash)
|
125
|
+
obj1_keys = obj1.keys
|
126
|
+
obj2_keys = obj2.keys
|
123
127
|
|
124
|
-
deleted_keys =
|
125
|
-
common_keys =
|
126
|
-
added_keys =
|
128
|
+
deleted_keys = (obj1_keys - obj2_keys).sort_by(&:to_s)
|
129
|
+
common_keys = (obj1_keys & obj2_keys).sort_by(&:to_s)
|
130
|
+
added_keys = (obj2_keys - obj1_keys).sort_by(&:to_s)
|
127
131
|
|
128
132
|
# add deleted properties
|
129
|
-
deleted_keys.
|
133
|
+
deleted_keys.each do |k|
|
130
134
|
change_key = prefix_append_key(opts[:prefix], k, opts)
|
131
135
|
custom_result = custom_compare(opts[:comparison], change_key, obj1[k], nil)
|
132
136
|
|
@@ -138,13 +142,13 @@ module HashDiff
|
|
138
142
|
end
|
139
143
|
|
140
144
|
# recursive comparison for common keys
|
141
|
-
common_keys.
|
145
|
+
common_keys.each do |k|
|
142
146
|
prefix = prefix_append_key(opts[:prefix], k, opts)
|
143
147
|
result.concat(diff(obj1[k], obj2[k], opts.merge(prefix: prefix)))
|
144
148
|
end
|
145
149
|
|
146
150
|
# added properties
|
147
|
-
added_keys.
|
151
|
+
added_keys.each do |k|
|
148
152
|
change_key = prefix_append_key(opts[:prefix], k, opts)
|
149
153
|
next if obj1.key?(k)
|
150
154
|
|
@@ -169,28 +173,33 @@ module HashDiff
|
|
169
173
|
#
|
170
174
|
# diff array using LCS algorithm
|
171
175
|
def self.diff_array_lcs(arraya, arrayb, options = {})
|
172
|
-
|
173
|
-
prefix: '',
|
174
|
-
similarity: 0.8,
|
175
|
-
delimiter: '.'
|
176
|
-
}.merge!(options)
|
176
|
+
return [] if arraya.empty? && arrayb.empty?
|
177
177
|
|
178
178
|
change_set = []
|
179
|
-
return [] if arraya.empty? && arrayb.empty?
|
180
179
|
|
181
180
|
if arraya.empty?
|
182
181
|
arrayb.each_index do |index|
|
183
182
|
change_set << ['+', index, arrayb[index]]
|
184
183
|
end
|
184
|
+
|
185
185
|
return change_set
|
186
|
-
|
186
|
+
end
|
187
|
+
|
188
|
+
if arrayb.empty?
|
187
189
|
arraya.each_index do |index|
|
188
190
|
i = arraya.size - index - 1
|
189
191
|
change_set << ['-', i, arraya[i]]
|
190
192
|
end
|
193
|
+
|
191
194
|
return change_set
|
192
195
|
end
|
193
196
|
|
197
|
+
opts = {
|
198
|
+
prefix: '',
|
199
|
+
similarity: 0.8,
|
200
|
+
delimiter: '.'
|
201
|
+
}.merge!(options)
|
202
|
+
|
194
203
|
links = lcs(arraya, arrayb, opts)
|
195
204
|
|
196
205
|
# yield common
|
data/lib/hashdiff/lcs.rb
CHANGED
@@ -1,15 +1,17 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module HashDiff
|
2
4
|
# @private
|
3
5
|
#
|
4
6
|
# caculate array difference using LCS algorithm
|
5
7
|
# http://en.wikipedia.org/wiki/Longest_common_subsequence_problem
|
6
8
|
def self.lcs(arraya, arrayb, options = {})
|
9
|
+
return [] if arraya.empty? || arrayb.empty?
|
10
|
+
|
7
11
|
opts = { similarity: 0.8 }.merge!(options)
|
8
12
|
|
9
13
|
opts[:prefix] = prefix_append_array_index(opts[:prefix], '*', opts)
|
10
14
|
|
11
|
-
return [] if arraya.empty? || arrayb.empty?
|
12
|
-
|
13
15
|
a_start = b_start = 0
|
14
16
|
a_finish = arraya.size - 1
|
15
17
|
b_finish = arrayb.size - 1
|
data/lib/hashdiff/patch.rb
CHANGED
data/lib/hashdiff/util.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module HashDiff
|
2
4
|
# @private
|
3
5
|
#
|
@@ -5,14 +7,15 @@ module HashDiff
|
|
5
7
|
def self.similar?(obja, objb, options = {})
|
6
8
|
return compare_values(obja, objb, options) unless obja.is_a?(Array) || obja.is_a?(Hash) || objb.is_a?(Array) || objb.is_a?(Hash)
|
7
9
|
|
8
|
-
opts = { similarity: 0.8 }.merge(options)
|
9
|
-
|
10
10
|
count_a = count_nodes(obja)
|
11
11
|
count_b = count_nodes(objb)
|
12
|
-
diffs = count_diff diff(obja, objb, opts)
|
13
12
|
|
14
13
|
return true if (count_a + count_b).zero?
|
15
14
|
|
15
|
+
opts = { similarity: 0.8 }.merge!(options)
|
16
|
+
|
17
|
+
diffs = count_diff diff(obja, objb, opts)
|
18
|
+
|
16
19
|
(1 - diffs.to_f / (count_a + count_b).to_f) >= opts[:similarity]
|
17
20
|
end
|
18
21
|
|
@@ -81,8 +84,8 @@ module HashDiff
|
|
81
84
|
#
|
82
85
|
# check for equality or "closeness" within given tolerance
|
83
86
|
def self.compare_values(obj1, obj2, options = {})
|
84
|
-
if
|
85
|
-
|
87
|
+
if options[:numeric_tolerance].is_a?(Numeric) &&
|
88
|
+
obj1.is_a?(Numeric) && obj2.is_a?(Numeric)
|
86
89
|
return (obj1 - obj2).abs <= options[:numeric_tolerance]
|
87
90
|
end
|
88
91
|
|
@@ -103,9 +106,7 @@ module HashDiff
|
|
103
106
|
#
|
104
107
|
# check if objects are comparable
|
105
108
|
def self.comparable?(obj1, obj2, strict = true)
|
106
|
-
|
107
|
-
return true if obj1.is_a?(type) && obj2.is_a?(type)
|
108
|
-
end
|
109
|
+
return true if (obj1.is_a?(Array) || obj1.is_a?(Hash)) && obj2.is_a?(obj1.class)
|
109
110
|
return true if !strict && obj1.is_a?(Numeric) && obj2.is_a?(Numeric)
|
110
111
|
|
111
112
|
obj1.is_a?(obj2.class) && obj2.is_a?(obj1.class)
|
data/lib/hashdiff/version.rb
CHANGED
data/spec/hash_diff/diff_spec.rb
CHANGED
data/spec/hash_diff/lcs_spec.rb
CHANGED
data/spec/hash_diff/util_spec.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'spec_helper'
|
2
4
|
|
3
5
|
describe HashDiff do
|
@@ -18,6 +20,14 @@ describe HashDiff do
|
|
18
20
|
described_class.similar?(a, b, similarity: 1).should be false
|
19
21
|
end
|
20
22
|
|
23
|
+
it 'is able to tell similiar empty hash' do
|
24
|
+
described_class.similar?({}, {}, 1).should be true
|
25
|
+
end
|
26
|
+
|
27
|
+
it 'is able to tell similiar empty array' do
|
28
|
+
described_class.similar?([], [], 1).should be true
|
29
|
+
end
|
30
|
+
|
21
31
|
it 'is able to tell similiar hash with values within tolerance' do
|
22
32
|
a = { 'a' => 1.5, 'b' => 2.25, 'c' => 3, 'd' => 4, 'e' => 5 }
|
23
33
|
b = { 'a' => 1.503, 'b' => 2.22, 'c' => 3, 'e' => 5 }
|
data/spec/spec_helper.rb
CHANGED
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.
|
4
|
+
version: 0.3.9
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Liu Fengyun
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2019-04-22 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bluecloth
|
@@ -126,7 +126,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
126
126
|
requirements:
|
127
127
|
- - ">="
|
128
128
|
- !ruby/object:Gem::Version
|
129
|
-
version:
|
129
|
+
version: 2.0.0
|
130
130
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
131
131
|
requirements:
|
132
132
|
- - ">="
|