slide_rule 0.2.0 → 1.0.1
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 +5 -5
- data/.gitignore +4 -0
- data/.rubocop.yml +2 -0
- data/README.md +44 -5
- data/config/rubocop/disabled.yml +4 -0
- data/lib/slide_rule/distance_calculator.rb +74 -13
- data/lib/slide_rule/distance_calculators/day_of_month.rb +3 -1
- data/lib/slide_rule/distance_calculators/day_of_year.rb +5 -4
- data/lib/slide_rule/distance_calculators/levenshtein.rb +1 -1
- data/lib/slide_rule/version.rb +1 -1
- data/slide_rule.gemspec +1 -1
- data/spec/slide_rule/distance_calculator_spec.rb +92 -13
- data/spec/slide_rule/distance_calculators/day_of_month_spec.rb +5 -0
- data/spec/slide_rule/distance_calculators/day_of_year_spec.rb +17 -0
- data/spec/slide_rule/distance_calculators/levenshtein_spec.rb +10 -2
- data/spec/spec_helper.rb +1 -0
- metadata +22 -21
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 38e1fef1daba59d9089c2d76e641bd3badbb63fc8f6ee41e1848a25d6c37ca97
|
4
|
+
data.tar.gz: c408e991827a4432a65635aac1a08ae4adfc85fbd7b6e07374db3521ea000a67
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: ec65e2cdf24fe70bf839a8885ecb42cb40a958aa592fe7a1cf360bd6b7bb1462afcb13e675e0dcfbfd89c4f5dc063f8e240e457b2f1041daf08ee303336f373b
|
7
|
+
data.tar.gz: 7911f9ed6c7043eb623c80984820fddc02130d1ec5c74681204441d7c644d83d6432d2f87778abb5a64849b415105b261cb1a3722faf0ed2c91e3340bf0fc730
|
data/.gitignore
CHANGED
data/.rubocop.yml
CHANGED
data/README.md
CHANGED
@@ -19,25 +19,26 @@ _Note: weights are assumed to be equal if not provided_
|
|
19
19
|
|
20
20
|
#API
|
21
21
|
|
22
|
-
##Describe the field calculators
|
22
|
+
##Describe the field distance calculators
|
23
23
|
|
24
24
|
Each field to be considered in the distance calculation should be described
|
25
25
|
with a calculation method and weight(optional)
|
26
26
|
|
27
27
|
Valid calculators:
|
28
28
|
|
29
|
-
*
|
30
|
-
*
|
29
|
+
* day_of_year
|
30
|
+
* day_of_month
|
31
|
+
* levenshtein
|
31
32
|
|
32
33
|
```ruby
|
33
34
|
distance_rules = {
|
34
35
|
:description => {
|
35
36
|
:weight => 0.80,
|
36
|
-
:
|
37
|
+
:calculator => :levenshtein,
|
37
38
|
},
|
38
39
|
:date => {
|
39
40
|
:weight => 0.90,
|
40
|
-
:
|
41
|
+
:calculator => :day_of_month,
|
41
42
|
},
|
42
43
|
}
|
43
44
|
```
|
@@ -81,3 +82,41 @@ matcher.closest_match(candidate, [example, example2], 0.2)
|
|
81
82
|
=> example
|
82
83
|
|
83
84
|
```
|
85
|
+
|
86
|
+
## Custom Field Distance Calculators
|
87
|
+
|
88
|
+
To define a custom field distance calculator, define a class with a `calculate(value1, value2)` method.
|
89
|
+
|
90
|
+
Requirements:
|
91
|
+
* Class must be stateless
|
92
|
+
* Calculate should return a float from `0` (perfect match) to `1.0` (no match)
|
93
|
+
* Calculation should not be order dependent (e.g. `calculate(a, b) == calculate(b, a)`)
|
94
|
+
|
95
|
+
```ruby
|
96
|
+
class StringLengthCalculator
|
97
|
+
def calculate(l1, l2)
|
98
|
+
diff = (l1 - l2).abs.to_f
|
99
|
+
return diff / [l1, l2].max
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
matcher = ::SlideRule::DistanceCalculator.new(
|
104
|
+
:length => {
|
105
|
+
:weight => 1.0,
|
106
|
+
:calculator => StringLengthCalculator
|
107
|
+
}
|
108
|
+
)
|
109
|
+
|
110
|
+
# Find the string with the closest length
|
111
|
+
matcher.closest_match("Howdy Doody Time!", ["Felix the cat", "Mighty Mouse"], 0.5)
|
112
|
+
# => { :item=>"Mighty Mouse", :distance=>0.29411764705882354 }
|
113
|
+
```
|
114
|
+
|
115
|
+
See the [distance_calculators](https://github.com/mattnichols/slide_rule/tree/master/lib/slide_rule/distance_calculators) directory in source for more examples.
|
116
|
+
|
117
|
+
|
118
|
+
# To Do
|
119
|
+
|
120
|
+
* Add more field distance calculators
|
121
|
+
|
122
|
+
|
@@ -1,7 +1,9 @@
|
|
1
1
|
module SlideRule
|
2
2
|
class DistanceCalculator
|
3
|
+
attr_accessor :rules
|
4
|
+
|
3
5
|
def initialize(rules)
|
4
|
-
@rules =
|
6
|
+
@rules = prepare_rules(rules)
|
5
7
|
end
|
6
8
|
|
7
9
|
# TODO: Figure this out. Very inefficient!
|
@@ -20,8 +22,15 @@ module SlideRule
|
|
20
22
|
end
|
21
23
|
end
|
22
24
|
|
23
|
-
def closest_match(obj, array, threshold)
|
24
|
-
matches(obj, array, threshold).
|
25
|
+
def closest_match(obj, array, threshold = 1.0)
|
26
|
+
matches(obj, array, threshold).sort_by { |match| match[:distance] }.first
|
27
|
+
end
|
28
|
+
|
29
|
+
def closest_matching_item(obj, array, threshold = 1.0)
|
30
|
+
match = closest_match(obj, array, threshold)
|
31
|
+
return nil if match.nil?
|
32
|
+
|
33
|
+
match[:item]
|
25
34
|
end
|
26
35
|
|
27
36
|
def is_match?(obj_1, obj_2, threshold)
|
@@ -32,7 +41,7 @@ module SlideRule
|
|
32
41
|
def matches(obj, array, threshold)
|
33
42
|
array.map do |item|
|
34
43
|
distance = calculate_distance(obj, item)
|
35
|
-
next nil unless distance
|
44
|
+
next nil unless distance <= threshold
|
36
45
|
{
|
37
46
|
item: item,
|
38
47
|
distance: distance
|
@@ -48,23 +57,41 @@ module SlideRule
|
|
48
57
|
# {
|
49
58
|
# :attribute_name => {
|
50
59
|
# :weight => 0.90,
|
51
|
-
# :
|
60
|
+
# :calculator => :distance_calculator,
|
61
|
+
# :threshold => 30
|
52
62
|
# }
|
53
63
|
# }
|
54
64
|
def calculate_distance(i1, i2)
|
55
|
-
|
65
|
+
calculate_weighted_distances(i1, i2).reduce(0.0) do |distance, obj|
|
66
|
+
distance + (obj[:distance] * obj[:weight])
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
private
|
71
|
+
|
72
|
+
def calculate_weighted_distances(i1, i2)
|
73
|
+
distances = @rules.map do |attribute, options|
|
56
74
|
val1 = i1.send(attribute)
|
57
75
|
val2 = i2.send(attribute)
|
58
|
-
|
59
|
-
|
60
|
-
|
76
|
+
distance = options[:calculator].calculate(val1, val2, options)
|
77
|
+
next { distance: distance.to_f, weight: options[:weight] } unless distance.nil?
|
78
|
+
|
79
|
+
nil
|
80
|
+
end
|
81
|
+
normalize_weights_array(distances) if distances.compact!
|
82
|
+
|
83
|
+
distances
|
61
84
|
end
|
62
85
|
|
63
86
|
def get_calculator(calculator)
|
64
87
|
return calculator.new if calculator.is_a?(Class)
|
65
88
|
|
66
|
-
klass_name =
|
67
|
-
klass =
|
89
|
+
klass_name = calculator.to_s.split('_').collect(&:capitalize).join.to_s
|
90
|
+
klass = begin
|
91
|
+
::SlideRule::DistanceCalculators.const_get(klass_name)
|
92
|
+
rescue ::NameError
|
93
|
+
nil
|
94
|
+
end
|
68
95
|
|
69
96
|
fail ArgumentError, "Unable to find calculator #{klass_name}" if klass.nil?
|
70
97
|
|
@@ -73,12 +100,46 @@ module SlideRule
|
|
73
100
|
|
74
101
|
# Ensures all weights add up to 1.0
|
75
102
|
#
|
76
|
-
def normalize_weights(
|
77
|
-
rules = rules_hash.dup
|
103
|
+
def normalize_weights(rules)
|
78
104
|
weight_total = rules.map { |_attr, rule| rule[:weight] }.reduce(0.0, &:+)
|
79
105
|
rules.each do |_attr, rule|
|
80
106
|
rule[:weight] = rule[:weight] / weight_total
|
81
107
|
end
|
82
108
|
end
|
109
|
+
|
110
|
+
# Ensures all weights add up to 1.0 in array of hashes
|
111
|
+
#
|
112
|
+
def normalize_weights_array(rules)
|
113
|
+
weight_total = rules.map { |rule| rule[:weight] }.reduce(0.0, &:+)
|
114
|
+
rules.each do |rule|
|
115
|
+
rule[:weight] = rule[:weight] / weight_total
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
# Prepares a duplicate of given rules hash with normalized weights and calculator instances
|
120
|
+
#
|
121
|
+
def prepare_rules(rules)
|
122
|
+
prepared_rules = rules.each_with_object({}) do |(attribute, rule), copy|
|
123
|
+
rule = copy[attribute] = safe_dup(rule)
|
124
|
+
|
125
|
+
if rule[:type]
|
126
|
+
puts 'Rule key `:type` is deprecated. Use `:calculator` instead.'
|
127
|
+
rule[:calculator] = rule[:type]
|
128
|
+
end
|
129
|
+
|
130
|
+
rule[:calculator] = get_calculator(rule[:calculator])
|
131
|
+
|
132
|
+
copy
|
133
|
+
end
|
134
|
+
prepared_rules = normalize_weights(prepared_rules)
|
135
|
+
|
136
|
+
prepared_rules
|
137
|
+
end
|
138
|
+
|
139
|
+
def safe_dup(obj)
|
140
|
+
obj.dup
|
141
|
+
rescue
|
142
|
+
obj
|
143
|
+
end
|
83
144
|
end
|
84
145
|
end
|
@@ -7,7 +7,8 @@ module SlideRule
|
|
7
7
|
# Calculates distance using 15 as the max point.
|
8
8
|
# Does not take into account the number of days in the actual month being considered.
|
9
9
|
#
|
10
|
-
def calculate(first, second)
|
10
|
+
def calculate(first, second, options={})
|
11
|
+
return nil if first.nil? || second.nil?
|
11
12
|
first = cleanse_date(first)
|
12
13
|
second = cleanse_date(second)
|
13
14
|
|
@@ -23,6 +24,7 @@ module SlideRule
|
|
23
24
|
private
|
24
25
|
|
25
26
|
def cleanse_date(date)
|
27
|
+
date = Time.at(date).utc.to_date if date.is_a?(::Integer)
|
26
28
|
date = Date.parse(date) unless date.is_a?(::Date) || date.is_a?(::Time)
|
27
29
|
date = date.to_date if date.is_a?(::Time)
|
28
30
|
|
@@ -3,21 +3,22 @@ module SlideRule
|
|
3
3
|
class DayOfYear
|
4
4
|
DAYS_IN_YEAR = 365
|
5
5
|
|
6
|
-
def calculate(date_1, date_2)
|
6
|
+
def calculate(date_1, date_2, options={})
|
7
7
|
date_1 = cleanse_date(date_1)
|
8
8
|
date_2 = cleanse_date(date_2)
|
9
|
-
|
9
|
+
threshold = options[:threshold] || DAYS_IN_YEAR
|
10
10
|
days_apart = (date_1.mjd - date_2.mjd).abs
|
11
11
|
|
12
|
-
return 1 if days_apart >=
|
12
|
+
return 1 if days_apart >= threshold
|
13
13
|
|
14
|
-
distance = days_apart.to_f /
|
14
|
+
distance = days_apart.to_f / threshold
|
15
15
|
distance.round(2)
|
16
16
|
end
|
17
17
|
|
18
18
|
private
|
19
19
|
|
20
20
|
def cleanse_date(date)
|
21
|
+
date = Time.at(date).utc.to_date if date.is_a?(::Integer)
|
21
22
|
date = Date.parse(date) unless date.is_a?(::Date) || date.is_a?(::Time)
|
22
23
|
date = date.to_date if date.is_a?(::Time)
|
23
24
|
|
data/lib/slide_rule/version.rb
CHANGED
data/slide_rule.gemspec
CHANGED
@@ -7,7 +7,7 @@ Gem::Specification.new do |s|
|
|
7
7
|
s.version = ::SlideRule::VERSION
|
8
8
|
s.authors = %w(mattnichols fergmastaflex)
|
9
9
|
s.email = ['dev@mx.com']
|
10
|
-
s.homepage = 'https://github.com/
|
10
|
+
s.homepage = 'https://github.com/mxenabled/slide_rule'
|
11
11
|
s.summary = 'Ruby object distance calculator'
|
12
12
|
s.description = 'Calculates the distance between 2 arbitrary objects using specified fields and algorithms.'
|
13
13
|
s.license = 'MIT'
|
@@ -14,11 +14,17 @@ describe ::SlideRule::DistanceCalculator do
|
|
14
14
|
end
|
15
15
|
|
16
16
|
class CustomCalc
|
17
|
-
def calculate(_first, _second)
|
17
|
+
def calculate(_first, _second, _options)
|
18
18
|
0.9
|
19
19
|
end
|
20
20
|
end
|
21
21
|
|
22
|
+
class NilCalc
|
23
|
+
def calculate(_first, _second, _options)
|
24
|
+
nil
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
22
28
|
let(:examples) do
|
23
29
|
[
|
24
30
|
::ExampleTransaction.new(amount: 25.00, date: '2015-02-05', description: 'Audible.com'),
|
@@ -36,22 +42,32 @@ describe ::SlideRule::DistanceCalculator do
|
|
36
42
|
::SlideRule::DistanceCalculator.new(
|
37
43
|
description: {
|
38
44
|
weight: 0.80,
|
39
|
-
|
45
|
+
calculator: :levenshtein
|
40
46
|
},
|
41
47
|
date: {
|
42
48
|
weight: 0.90,
|
43
|
-
|
49
|
+
calculator: :day_of_month
|
44
50
|
}
|
45
51
|
)
|
46
52
|
end
|
47
53
|
|
48
|
-
it 'finds
|
54
|
+
it 'finds closest' do
|
49
55
|
example = ExampleTransaction.new(description: 'Wells Fargo Dealer SVC', date: '2015-06-17')
|
50
56
|
expect(calculator.closest_match(example, examples, 0.2)[:item]).to eq(examples[3])
|
51
57
|
|
52
58
|
example = ExampleTransaction.new(description: 'Audible.com', date: '2015-06-05')
|
53
59
|
expect(calculator.closest_match(example, examples, 0.2)[:item]).to eq(examples[0])
|
54
60
|
end
|
61
|
+
|
62
|
+
it 'with default threshold' do
|
63
|
+
example = ExampleTransaction.new(description: 'Audible.com', date: '2015-06-05')
|
64
|
+
expect(calculator.closest_match(example, examples)[:item]).to eq(examples[0])
|
65
|
+
end
|
66
|
+
|
67
|
+
it 'finds closest matching item' do
|
68
|
+
example = ExampleTransaction.new(description: 'Audible.com', date: '2015-06-05')
|
69
|
+
expect(calculator.closest_matching_item(example, examples)).to eq(examples[0])
|
70
|
+
end
|
55
71
|
end
|
56
72
|
|
57
73
|
describe '#is_match?' do
|
@@ -59,23 +75,23 @@ describe ::SlideRule::DistanceCalculator do
|
|
59
75
|
::SlideRule::DistanceCalculator.new(
|
60
76
|
description: {
|
61
77
|
weight: 0.80,
|
62
|
-
|
78
|
+
calculator: :levenshtein
|
63
79
|
},
|
64
80
|
date: {
|
65
81
|
weight: 0.90,
|
66
|
-
|
82
|
+
calculator: :day_of_month
|
67
83
|
}
|
68
84
|
)
|
69
85
|
end
|
70
86
|
|
71
|
-
it 'returns true if there is a match' do
|
87
|
+
it 'returns true if there is a match' do
|
72
88
|
example_1 = ExampleTransaction.new(description: 'Wells Fargo Dealer SVC', date: '2015-06-17')
|
73
89
|
example_2 = ExampleTransaction.new(description: 'Wells Fargo Dealer SVC', date: '2015-06-17')
|
74
90
|
|
75
91
|
expect(calculator.is_match?(example_1, example_2, 0.2)).to be(true)
|
76
92
|
end
|
77
93
|
|
78
|
-
it 'returns false if there is a match' do
|
94
|
+
it 'returns false if there is a match' do
|
79
95
|
example_1 = ExampleTransaction.new(description: 'Wells Fargo Dealer SVC', date: '2015-06-17')
|
80
96
|
example_2 = ExampleTransaction.new(description: 'Taco Bell', date: '2015-06-17')
|
81
97
|
|
@@ -89,11 +105,11 @@ describe ::SlideRule::DistanceCalculator do
|
|
89
105
|
calculator = ::SlideRule::DistanceCalculator.new(
|
90
106
|
description: {
|
91
107
|
weight: 1.00,
|
92
|
-
|
108
|
+
calculator: :levenshtein
|
93
109
|
},
|
94
110
|
date: {
|
95
111
|
weight: 0.50,
|
96
|
-
|
112
|
+
calculator: :day_of_month
|
97
113
|
}
|
98
114
|
)
|
99
115
|
example = ::ExampleTransaction.new(amount: 25.00, date: '2015-02-05', description: 'Audible.com')
|
@@ -105,11 +121,11 @@ describe ::SlideRule::DistanceCalculator do
|
|
105
121
|
calculator = ::SlideRule::DistanceCalculator.new(
|
106
122
|
description: {
|
107
123
|
weight: 0.50,
|
108
|
-
|
124
|
+
calculator: :levenshtein
|
109
125
|
},
|
110
126
|
date: {
|
111
127
|
weight: 0.50,
|
112
|
-
|
128
|
+
calculator: :day_of_month
|
113
129
|
}
|
114
130
|
)
|
115
131
|
example = ::ExampleTransaction.new(amount: 25.00, date: '2015-02-05', description: 'Audible.com')
|
@@ -125,6 +141,23 @@ describe ::SlideRule::DistanceCalculator do
|
|
125
141
|
distance = calculator.calculate_distance(example, candidate)
|
126
142
|
expect(distance.round(4)).to eq(((3.0 * 0.5 / 15) + (4.0 * 0.5 / 11)).round(4))
|
127
143
|
end
|
144
|
+
|
145
|
+
it 'should renormalize on nil' do
|
146
|
+
calculator = ::SlideRule::DistanceCalculator.new(
|
147
|
+
description: {
|
148
|
+
weight: 0.50,
|
149
|
+
calculator: :levenshtein
|
150
|
+
},
|
151
|
+
date: {
|
152
|
+
weight: 0.50,
|
153
|
+
calculator: NilCalc
|
154
|
+
}
|
155
|
+
)
|
156
|
+
example1 = ::ExampleTransaction.new(amount: 25.00, date: '2015-02-05', description: 'Audible.com')
|
157
|
+
example2 = ::ExampleTransaction.new(amount: 25.00, date: '2015-06-08', description: 'Audible Inc')
|
158
|
+
|
159
|
+
expect(calculator.calculate_distance(example1, example2).round(4)).to eq((4.0 / 11).round(4))
|
160
|
+
end
|
128
161
|
end
|
129
162
|
|
130
163
|
context 'uses custom calculator' do
|
@@ -132,7 +165,7 @@ describe ::SlideRule::DistanceCalculator do
|
|
132
165
|
calculator = ::SlideRule::DistanceCalculator.new(
|
133
166
|
description: {
|
134
167
|
weight: 1.00,
|
135
|
-
|
168
|
+
calculator: CustomCalc
|
136
169
|
}
|
137
170
|
)
|
138
171
|
example = ::ExampleTransaction.new
|
@@ -142,5 +175,51 @@ describe ::SlideRule::DistanceCalculator do
|
|
142
175
|
expect(distance).to eq(0.9)
|
143
176
|
end
|
144
177
|
end
|
178
|
+
|
179
|
+
describe '#initialize' do
|
180
|
+
context 'validates rules on initialize' do
|
181
|
+
it 'should allow :type' do
|
182
|
+
::SlideRule::DistanceCalculator.new(
|
183
|
+
description: {
|
184
|
+
weight: 1.00,
|
185
|
+
type: CustomCalc
|
186
|
+
}
|
187
|
+
)
|
188
|
+
end
|
189
|
+
|
190
|
+
it 'should not modify input rule hash' do
|
191
|
+
rules = {
|
192
|
+
description: {
|
193
|
+
weight: 1.0,
|
194
|
+
type: CustomCalc
|
195
|
+
},
|
196
|
+
name: {
|
197
|
+
weight: 1.0,
|
198
|
+
type: CustomCalc
|
199
|
+
}
|
200
|
+
}
|
201
|
+
::SlideRule::DistanceCalculator.new(rules)
|
202
|
+
# Run a second time to ensure that no calculator instance is in rules. Will currently throw an error.
|
203
|
+
::SlideRule::DistanceCalculator.new(rules)
|
204
|
+
|
205
|
+
# :type should still be in original hash
|
206
|
+
expect(rules[:name].key?(:calculator)).to eq(false)
|
207
|
+
|
208
|
+
# :weight should not be normalized in original hash
|
209
|
+
expect(rules[:name][:weight]).to eq(1.0)
|
210
|
+
end
|
211
|
+
|
212
|
+
it 'should raise error if not valid calculator' do
|
213
|
+
expect do
|
214
|
+
::SlideRule::DistanceCalculator.new(
|
215
|
+
description: {
|
216
|
+
weight: 1.00,
|
217
|
+
calculator: :some_junk
|
218
|
+
}
|
219
|
+
)
|
220
|
+
end.to raise_error(::ArgumentError, 'Unable to find calculator SomeJunk')
|
221
|
+
end
|
222
|
+
end
|
223
|
+
end
|
145
224
|
end
|
146
225
|
end
|
@@ -1,4 +1,5 @@
|
|
1
1
|
require 'spec_helper'
|
2
|
+
require 'date'
|
2
3
|
|
3
4
|
describe ::SlideRule::DistanceCalculators::DayOfMonth do
|
4
5
|
describe '#calculate' do
|
@@ -6,6 +7,10 @@ describe ::SlideRule::DistanceCalculators::DayOfMonth do
|
|
6
7
|
expect(described_class.new.calculate('2012-03-19', '2014-08-19')).to eq(0.0)
|
7
8
|
end
|
8
9
|
|
10
|
+
it 'should accept epoch date' do
|
11
|
+
expect(described_class.new.calculate(1_444_262_400, 1_444_262_400)).to eq(0.0)
|
12
|
+
end
|
13
|
+
|
9
14
|
it 'should calculate when date is in the same month' do
|
10
15
|
expect(described_class.new.calculate('2012-03-19', '2014-08-22')).to eq(3.0 / 15)
|
11
16
|
expect(described_class.new.calculate('2012-03-19', '2014-08-09')).to eq(10.0 / 15)
|
@@ -5,6 +5,10 @@ describe ::SlideRule::DistanceCalculators::DayOfYear do
|
|
5
5
|
it 'should return a 0 distance' do
|
6
6
|
expect(described_class.new.calculate('2015-10-8', '2015-10-8')).to eq(0.0)
|
7
7
|
end
|
8
|
+
|
9
|
+
it 'should accept epoch date' do
|
10
|
+
expect(described_class.new.calculate(1_444_262_400, 1_444_262_400)).to eq(0.0)
|
11
|
+
end
|
8
12
|
end
|
9
13
|
|
10
14
|
context 'when dates are more than a year apart' do
|
@@ -18,4 +22,17 @@ describe ::SlideRule::DistanceCalculators::DayOfYear do
|
|
18
22
|
expect(described_class.new.calculate('2015-10-8', '2015-11-8')).to eq(0.08)
|
19
23
|
end
|
20
24
|
end
|
25
|
+
|
26
|
+
context 'when there is a threshold' do
|
27
|
+
it 'should return a 1 distance when there are too many days apart' do
|
28
|
+
expect(described_class.new.calculate('2016-02-03', '2016-03-09', :threshold => 30)).to eq(1)
|
29
|
+
end
|
30
|
+
|
31
|
+
it 'should return a more sane number' do
|
32
|
+
result_with_threshold = described_class.new.calculate('2016-02-03', '2016-02-10', :threshold => 30)
|
33
|
+
result_without_threshold = described_class.new.calculate('2016-02-03', '2016-02-10')
|
34
|
+
|
35
|
+
expect(result_with_threshold).to be > result_without_threshold
|
36
|
+
end
|
37
|
+
end
|
21
38
|
end
|
@@ -1,11 +1,19 @@
|
|
1
1
|
require 'spec_helper'
|
2
2
|
|
3
3
|
describe ::SlideRule::DistanceCalculators::Levenshtein do
|
4
|
+
let(:subject) { described_class.new }
|
5
|
+
|
4
6
|
it 'should calculate perfect match' do
|
5
|
-
expect(
|
7
|
+
expect(subject.calculate('this is a test', 'this is a test')).to eq(0.0)
|
6
8
|
end
|
7
9
|
|
8
10
|
it 'should calculate distance as distance divided by length of longest string' do
|
9
|
-
expect(
|
11
|
+
expect(subject.calculate('this is a test', 'this is a test!').round(4)).to eq((1.0 / 15).round(4))
|
12
|
+
end
|
13
|
+
|
14
|
+
it 'should handle nils' do
|
15
|
+
expect(subject.calculate(nil, nil)).to eq(0.0)
|
16
|
+
expect(subject.calculate(nil, 'goodbye')).to eq(1.0)
|
17
|
+
expect(subject.calculate('hello', nil)).to eq(1.0)
|
10
18
|
end
|
11
19
|
end
|
data/spec/spec_helper.rb
CHANGED
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: slide_rule
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 1.0.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- mattnichols
|
@@ -9,12 +9,12 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date:
|
12
|
+
date: 2021-08-26 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
requirement: !ruby/object:Gem::Requirement
|
16
16
|
requirements:
|
17
|
-
- - ~>
|
17
|
+
- - "~>"
|
18
18
|
- !ruby/object:Gem::Version
|
19
19
|
version: '1.0'
|
20
20
|
name: vladlev
|
@@ -22,13 +22,13 @@ dependencies:
|
|
22
22
|
type: :runtime
|
23
23
|
version_requirements: !ruby/object:Gem::Requirement
|
24
24
|
requirements:
|
25
|
-
- - ~>
|
25
|
+
- - "~>"
|
26
26
|
- !ruby/object:Gem::Version
|
27
27
|
version: '1.0'
|
28
28
|
- !ruby/object:Gem::Dependency
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
30
30
|
requirements:
|
31
|
-
- - ~>
|
31
|
+
- - "~>"
|
32
32
|
- !ruby/object:Gem::Version
|
33
33
|
version: '10'
|
34
34
|
name: rake
|
@@ -36,13 +36,13 @@ dependencies:
|
|
36
36
|
type: :development
|
37
37
|
version_requirements: !ruby/object:Gem::Requirement
|
38
38
|
requirements:
|
39
|
-
- - ~>
|
39
|
+
- - "~>"
|
40
40
|
- !ruby/object:Gem::Version
|
41
41
|
version: '10'
|
42
42
|
- !ruby/object:Gem::Dependency
|
43
43
|
requirement: !ruby/object:Gem::Requirement
|
44
44
|
requirements:
|
45
|
-
- - ~>
|
45
|
+
- - "~>"
|
46
46
|
- !ruby/object:Gem::Version
|
47
47
|
version: '0'
|
48
48
|
name: pry
|
@@ -50,13 +50,13 @@ dependencies:
|
|
50
50
|
type: :development
|
51
51
|
version_requirements: !ruby/object:Gem::Requirement
|
52
52
|
requirements:
|
53
|
-
- - ~>
|
53
|
+
- - "~>"
|
54
54
|
- !ruby/object:Gem::Version
|
55
55
|
version: '0'
|
56
56
|
- !ruby/object:Gem::Dependency
|
57
57
|
requirement: !ruby/object:Gem::Requirement
|
58
58
|
requirements:
|
59
|
-
- - ~>
|
59
|
+
- - "~>"
|
60
60
|
- !ruby/object:Gem::Version
|
61
61
|
version: '3'
|
62
62
|
name: rspec
|
@@ -64,13 +64,13 @@ dependencies:
|
|
64
64
|
type: :development
|
65
65
|
version_requirements: !ruby/object:Gem::Requirement
|
66
66
|
requirements:
|
67
|
-
- - ~>
|
67
|
+
- - "~>"
|
68
68
|
- !ruby/object:Gem::Version
|
69
69
|
version: '3'
|
70
70
|
- !ruby/object:Gem::Dependency
|
71
71
|
requirement: !ruby/object:Gem::Requirement
|
72
72
|
requirements:
|
73
|
-
- - ~>
|
73
|
+
- - "~>"
|
74
74
|
- !ruby/object:Gem::Version
|
75
75
|
version: '0'
|
76
76
|
name: rubocop
|
@@ -78,24 +78,26 @@ dependencies:
|
|
78
78
|
type: :development
|
79
79
|
version_requirements: !ruby/object:Gem::Requirement
|
80
80
|
requirements:
|
81
|
-
- - ~>
|
81
|
+
- - "~>"
|
82
82
|
- !ruby/object:Gem::Version
|
83
83
|
version: '0'
|
84
|
-
description: Calculates the distance between 2 arbitrary objects using specified fields
|
84
|
+
description: Calculates the distance between 2 arbitrary objects using specified fields
|
85
|
+
and algorithms.
|
85
86
|
email:
|
86
87
|
- dev@mx.com
|
87
88
|
executables: []
|
88
89
|
extensions: []
|
89
90
|
extra_rdoc_files: []
|
90
91
|
files:
|
91
|
-
- .gitignore
|
92
|
-
- .rubocop.yml
|
93
|
-
- .travis.yml
|
92
|
+
- ".gitignore"
|
93
|
+
- ".rubocop.yml"
|
94
|
+
- ".travis.yml"
|
94
95
|
- CODE_OF_CONDUCT.md
|
95
96
|
- Gemfile
|
96
97
|
- LICENSE
|
97
98
|
- README.md
|
98
99
|
- Rakefile
|
100
|
+
- config/rubocop/disabled.yml
|
99
101
|
- lib/slide_rule.rb
|
100
102
|
- lib/slide_rule/distance_calculator.rb
|
101
103
|
- lib/slide_rule/distance_calculators/day_of_month.rb
|
@@ -108,7 +110,7 @@ files:
|
|
108
110
|
- spec/slide_rule/distance_calculators/day_of_year_spec.rb
|
109
111
|
- spec/slide_rule/distance_calculators/levenshtein_spec.rb
|
110
112
|
- spec/spec_helper.rb
|
111
|
-
homepage: https://github.com/
|
113
|
+
homepage: https://github.com/mxenabled/slide_rule
|
112
114
|
licenses:
|
113
115
|
- MIT
|
114
116
|
metadata: {}
|
@@ -118,17 +120,16 @@ require_paths:
|
|
118
120
|
- lib
|
119
121
|
required_ruby_version: !ruby/object:Gem::Requirement
|
120
122
|
requirements:
|
121
|
-
- -
|
123
|
+
- - ">="
|
122
124
|
- !ruby/object:Gem::Version
|
123
125
|
version: '0'
|
124
126
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
125
127
|
requirements:
|
126
|
-
- -
|
128
|
+
- - ">="
|
127
129
|
- !ruby/object:Gem::Version
|
128
130
|
version: '0'
|
129
131
|
requirements: []
|
130
|
-
|
131
|
-
rubygems_version: 2.4.8
|
132
|
+
rubygems_version: 3.2.15
|
132
133
|
signing_key:
|
133
134
|
specification_version: 4
|
134
135
|
summary: Ruby object distance calculator
|