fuzzy_match 1.2.1 → 1.2.2
Sign up to get free protection for your applications and to get access to all the features.
- data/README.markdown +47 -27
- data/Rakefile +1 -5
- data/fuzzy_match.gemspec +3 -1
- data/lib/fuzzy_match.rb +1 -1
- data/lib/fuzzy_match/normalizer.rb +1 -1
- data/lib/fuzzy_match/score.rb +39 -36
- data/lib/fuzzy_match/similarity.rb +1 -1
- data/lib/fuzzy_match/stop_word.rb +1 -1
- data/lib/fuzzy_match/version.rb +1 -1
- data/lib/fuzzy_match/wrapper.rb +5 -3
- data/test/helper.rb +3 -0
- data/test/test_fuzzy_match.rb +5 -1
- data/test/test_wrapper.rb +29 -0
- metadata +44 -20
data/README.markdown
CHANGED
@@ -1,35 +1,44 @@
|
|
1
1
|
# fuzzy_match
|
2
2
|
|
3
|
-
Find a needle in a haystack based on string similarity
|
3
|
+
Find a needle in a haystack based on string similarity and regular expression rules.
|
4
4
|
|
5
5
|
Replaces [`loose_tight_dictionary`](https://github.com/seamusabshere/loose_tight_dictionary) because that was a confusing name.
|
6
6
|
|
7
7
|
## Quickstart
|
8
8
|
|
9
9
|
>> require 'fuzzy_match'
|
10
|
-
=> true
|
11
|
-
>> FuzzyMatch.new(['seamus', 'andy', 'ben'])
|
10
|
+
=> true
|
11
|
+
>> matcher = FuzzyMatch.new(['seamus', 'andy', 'ben'])
|
12
|
+
=> #<FuzzyMatch: [...]>
|
13
|
+
>> matcher.find('Shamus')
|
12
14
|
=> "seamus"
|
13
15
|
|
14
16
|
## Default matching (string similarity)
|
15
17
|
|
16
|
-
|
18
|
+
At the core, and even if you configure nothing else, string similarity (calculated by "pair distance" aka Dice's) is used to compare records.
|
17
19
|
|
18
|
-
|
20
|
+
You can tell `FuzzyMatch` what field or method to use via the `:read` option... for example, let's say you want to match a `Country` object like `#<Country name:"Uruguay" iso_3166_code:"UY">`
|
19
21
|
|
20
|
-
|
22
|
+
>> matcher = FuzzyMatch.new(Country.all, :read => :name) # Country#name will be called when comparing
|
23
|
+
=> #<FuzzyMatch: [...]>
|
24
|
+
>> matcher.find('youruguay')
|
25
|
+
=> #<Country name:"Uruguay" iso_3166_code:"UY"> # the matcher returns a Country object
|
21
26
|
|
22
|
-
|
27
|
+
## Optional rules (regular expressions)
|
23
28
|
|
24
|
-
|
25
|
-
|
26
|
-
|
29
|
+
You can improve the default matchings with rules. There are 4 different kinds of rules. Each rule is a regular expression. Depending on the kind of rule, the results of running the regular expression are used for a particular purpose.
|
30
|
+
|
31
|
+
We suggest that you **first try without any rules** and only define them to improve matching, prevent false positives, etc.
|
32
|
+
|
33
|
+
>> matcher = FuzzyMatch.new(['Ford F-150', 'Ford F-250', 'GMC 1500', 'GMC 2500'], :blockings => [ /ford/i, /gmc/i ], :normalizers => [ /K(\d500)/i ], :identities => [ /(f)-?(\d50)/i ])
|
27
34
|
=> #<FuzzyMatch: [...]>
|
28
35
|
>> matcher.find('fordf250')
|
29
36
|
=> "Ford F-250"
|
30
37
|
>> matcher.find('gmc truck k1500')
|
31
38
|
=> "GMC 1500"
|
32
39
|
|
40
|
+
For identities and normalizers (see below), **only the captures are used.** For example, `/(f)-?(\d50)/i` captures the "F" and the "250" but ignores the dash. So place your parentheses carefully! Blockings work the same way, except that if you don't have any captures, a simple match will pass.
|
41
|
+
|
33
42
|
### Blockings
|
34
43
|
|
35
44
|
Group records together.
|
@@ -40,13 +49,13 @@ Setting a blocking of `/Airbus/` ensures that strings containing "Airbus" will o
|
|
40
49
|
|
41
50
|
Strip strings down to the essentials.
|
42
51
|
|
43
|
-
Adding a normalizer like `/(boeing).*(7\d\d)/i` will cause "BOEING COMPANY 747" and "boeing747" to be
|
52
|
+
Adding a normalizer like `/(boeing).*(7\d\d)/i` will cause "BOEING COMPANY 747" and "boeing747" to be normalized to "BOEING 747" and "boeing 747", respectively. Since things are generally downcased before they are compared, these would be an exact match.
|
44
53
|
|
45
54
|
### Identities
|
46
55
|
|
47
56
|
Prevent impossible matches.
|
48
57
|
|
49
|
-
Adding an identity like `/(
|
58
|
+
Adding an identity like `/(f)-?(\d50)/i` ensures that "Ford F-150" and "Ford F-250" never match.
|
50
59
|
|
51
60
|
### Stop words
|
52
61
|
|
@@ -58,37 +67,48 @@ Adding a stop word like `THE` ensures that it is not taken into account when com
|
|
58
67
|
|
59
68
|
* `read`: how to interpret each record in the 'haystack', either a Proc or a symbol
|
60
69
|
* `must_match_blocking`: don't return a match unless the needle fits into one of the blockings you specified
|
61
|
-
* `must_match_at_least_one_word`: don't return a match unless the needle shares at least one word with the match
|
70
|
+
* `must_match_at_least_one_word`: don't return a match unless the needle shares at least one word with the match. Note that "Foo's" is treated like one word (so that it won't match "'s") and "Bolivia," is treated as just "bolivia"
|
62
71
|
* `first_blocking_decides`: force records into the first blocking they match, rather than choosing a blocking that will give them a higher score
|
63
72
|
* `gather_last_result`: enable `last_result`
|
64
73
|
|
65
|
-
### `:read`
|
66
|
-
|
67
|
-
So, what if your needle is a string like `youruguay` and your haystack is full of `Country` objects like `<Country name:"Uruguay">`?
|
68
|
-
|
69
|
-
>> FuzzyMatch.new(Country.all, :read => :name).find('youruguay')
|
70
|
-
=> <Country name:"Uruguay">
|
71
|
-
|
72
74
|
## Case sensitivity
|
73
75
|
|
74
76
|
String similarity is case-insensitive. Everything is downcased before scoring. This is a change from previous versions.
|
75
77
|
|
76
78
|
Be careful when trying to use case-sensitivity in your rules; in general, things are downcased before comparing.
|
77
79
|
|
78
|
-
##
|
80
|
+
## String similarity algorithm
|
81
|
+
|
82
|
+
The algorithm is [Dice's Coefficient](http://en.wikipedia.org/wiki/Dice's_coefficient) (aka Pair Distance) because it seemed to work better than Longest Substring, Hamming, Jaro Winkler, Levenshtein (although see edge case below) etc.
|
83
|
+
|
84
|
+
Here's a great explanation copied from [the wikipedia entry](http://en.wikipedia.org/wiki/Dice%27s_coefficient):
|
85
|
+
|
86
|
+
to calculate the similarity between:
|
87
|
+
|
88
|
+
night
|
89
|
+
nacht
|
90
|
+
|
91
|
+
We would find the set of bigrams in each word:
|
92
|
+
|
93
|
+
{ni,ig,gh,ht}
|
94
|
+
{na,ac,ch,ht}
|
95
|
+
|
96
|
+
Each set has four elements, and the intersection of these two sets has only one element: ht.
|
97
|
+
|
98
|
+
Inserting these numbers into the formula, we calculate, s = (2 · 1) / (4 + 4) = 0.25.
|
99
|
+
|
100
|
+
### Edge case: when Dice's fails, use Levenshtein
|
79
101
|
|
80
102
|
In edge cases where Dice's finds that two strings are equally similar to a third string, then Levenshtein distance is used. For example, pair distance considers "RATZ" and "CATZ" to be equally similar to "RITZ" so we invoke Levenshtein.
|
81
103
|
|
82
|
-
>> require 'amatch'
|
83
|
-
=> true
|
84
104
|
>> 'RITZ'.pair_distance_similar 'RATZ'
|
85
105
|
=> 0.3333333333333333
|
86
|
-
>> 'RITZ'.pair_distance_similar 'CATZ'
|
87
|
-
=> 0.3333333333333333
|
106
|
+
>> 'RITZ'.pair_distance_similar 'CATZ'
|
107
|
+
=> 0.3333333333333333 # pair distance can't tell the difference, so we fall back to levenshtein...
|
88
108
|
>> 'RITZ'.levenshtein_similar 'RATZ'
|
89
109
|
=> 0.75
|
90
|
-
>> 'RITZ'.levenshtein_similar 'CATZ'
|
91
|
-
=> 0.5
|
110
|
+
>> 'RITZ'.levenshtein_similar 'CATZ'
|
111
|
+
=> 0.5 # which properly shows that RATZ should win
|
92
112
|
|
93
113
|
## Production use
|
94
114
|
|
data/Rakefile
CHANGED
@@ -11,8 +11,4 @@ end
|
|
11
11
|
task :default => :test
|
12
12
|
|
13
13
|
require 'yard'
|
14
|
-
|
15
|
-
YARD::Rake::YardocTask.new do |t|
|
16
|
-
t.files = ['lib/**/*.rb', 'README.markdown'] # optional
|
17
|
-
# t.options = ['--any', '--extra', '--opts'] # optional
|
18
|
-
end
|
14
|
+
YARD::Rake::YardocTask.new
|
data/fuzzy_match.gemspec
CHANGED
@@ -26,7 +26,9 @@ Gem::Specification.new do |s|
|
|
26
26
|
s.add_development_dependency 'cohort_scope'
|
27
27
|
s.add_development_dependency 'weighted_average'
|
28
28
|
s.add_development_dependency 'rake'
|
29
|
-
|
29
|
+
s.add_development_dependency 'yard'
|
30
|
+
s.add_development_dependency 'amatch'
|
31
|
+
|
30
32
|
s.add_runtime_dependency 'activesupport', '>=3'
|
31
33
|
s.add_runtime_dependency 'to_regexp', '>=0.0.3'
|
32
34
|
end
|
data/lib/fuzzy_match.rb
CHANGED
data/lib/fuzzy_match/score.rb
CHANGED
@@ -1,22 +1,19 @@
|
|
1
|
-
begin
|
2
|
-
require 'amatch'
|
3
|
-
rescue ::LoadError
|
4
|
-
# using native ruby similarity scoring
|
5
|
-
end
|
6
|
-
|
7
1
|
class FuzzyMatch
|
8
2
|
class Score
|
9
|
-
|
3
|
+
extend ::ActiveSupport::Memoizable
|
4
|
+
|
5
|
+
attr_reader :str1
|
6
|
+
attr_reader :str2
|
10
7
|
|
11
8
|
def initialize(str1, str2)
|
12
9
|
@str1 = str1.downcase
|
13
10
|
@str2 = str2.downcase
|
14
11
|
end
|
15
|
-
|
12
|
+
|
16
13
|
def inspect
|
17
|
-
%{#<Score:
|
14
|
+
%{#<FuzzyMatch::Score: str1=#{str1.inspect} str2=#{str2.inspect} dices_coefficient_similar=#{dices_coefficient_similar} levenshtein_similar=#{levenshtein_similar}>}
|
18
15
|
end
|
19
|
-
|
16
|
+
|
20
17
|
def <=>(other)
|
21
18
|
by_dices_coefficient = (dices_coefficient_similar <=> other.dices_coefficient_similar)
|
22
19
|
if by_dices_coefficient == 0
|
@@ -25,23 +22,26 @@ class FuzzyMatch
|
|
25
22
|
by_dices_coefficient
|
26
23
|
end
|
27
24
|
end
|
28
|
-
|
29
|
-
def utf8?
|
30
|
-
(defined?(::Encoding) ? str1.encoding.to_s : $KCODE).downcase.start_with?('u')
|
31
|
-
end
|
32
|
-
|
25
|
+
|
33
26
|
if defined?(::Amatch)
|
34
|
-
|
27
|
+
|
35
28
|
def dices_coefficient_similar
|
29
|
+
if str1 == str2
|
30
|
+
return 1.0
|
31
|
+
elsif str1.length == 1 and str2.length == 1
|
32
|
+
return 0.0
|
33
|
+
end
|
36
34
|
str1.pair_distance_similar str2
|
37
35
|
end
|
38
|
-
|
36
|
+
memoize :dices_coefficient_similar
|
37
|
+
|
39
38
|
def levenshtein_similar
|
40
39
|
str1.levenshtein_similar str2
|
41
40
|
end
|
42
|
-
|
41
|
+
memoize :levenshtein_similar
|
42
|
+
|
43
43
|
else
|
44
|
-
|
44
|
+
|
45
45
|
SPACE = ' '
|
46
46
|
# http://stackoverflow.com/questions/653157/a-better-similarity-ranking-algorithm-for-variable-length-strings
|
47
47
|
def dices_coefficient_similar
|
@@ -60,19 +60,26 @@ class FuzzyMatch
|
|
60
60
|
end.reject do |pair|
|
61
61
|
pair.include? SPACE
|
62
62
|
end
|
63
|
-
union = pairs1.size + pairs2.size
|
64
|
-
intersection = 0
|
65
|
-
pairs1.each do |p1|
|
66
|
-
0.upto(pairs2.size-1) do |i|
|
67
|
-
if p1 == pairs2[i]
|
68
|
-
intersection += 1
|
69
|
-
pairs2.slice!(i)
|
70
|
-
break
|
71
|
-
end
|
72
|
-
end
|
73
|
-
end
|
63
|
+
union = pairs1.size + pairs2.size
|
64
|
+
intersection = 0
|
65
|
+
pairs1.each do |p1|
|
66
|
+
0.upto(pairs2.size-1) do |i|
|
67
|
+
if p1 == pairs2[i]
|
68
|
+
intersection += 1
|
69
|
+
pairs2.slice!(i)
|
70
|
+
break
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
74
|
(2.0 * intersection) / union
|
75
75
|
end
|
76
|
+
memoize :dices_coefficient_similar
|
77
|
+
|
78
|
+
# this seems like it would slow things down
|
79
|
+
def utf8?
|
80
|
+
(defined?(::Encoding) ? str1.encoding.to_s : $KCODE).downcase.start_with?('u')
|
81
|
+
end
|
82
|
+
memoize :utf8?
|
76
83
|
|
77
84
|
# extracted/adapted from the text gem version 1.0.2
|
78
85
|
# normalization added for utf-8 strings
|
@@ -114,12 +121,8 @@ class FuzzyMatch
|
|
114
121
|
# }
|
115
122
|
1.0 - x.to_f / [n, m].max
|
116
123
|
end
|
117
|
-
|
124
|
+
memoize :levenshtein_similar
|
125
|
+
|
118
126
|
end
|
119
|
-
|
120
|
-
extend ::ActiveSupport::Memoizable
|
121
|
-
memoize :dices_coefficient_similar
|
122
|
-
memoize :levenshtein_similar
|
123
|
-
memoize :utf8?
|
124
127
|
end
|
125
128
|
end
|
@@ -47,7 +47,7 @@ class FuzzyMatch
|
|
47
47
|
end
|
48
48
|
|
49
49
|
def inspect
|
50
|
-
%{#<Similarity
|
50
|
+
%{#<FuzzyMatch::Similarity #{wrapper2.render.inspect}=>#{best_wrapper2_variant.inspect} versus #{wrapper1.render.inspect}=>#{best_wrapper1_variant.inspect} original_weight=#{"%0.5f" % original_weight} best_score=#{best_score.inspect}>}
|
51
51
|
end
|
52
52
|
end
|
53
53
|
end
|
data/lib/fuzzy_match/version.rb
CHANGED
data/lib/fuzzy_match/wrapper.rb
CHANGED
@@ -13,7 +13,7 @@ class FuzzyMatch
|
|
13
13
|
end
|
14
14
|
|
15
15
|
def inspect
|
16
|
-
"#<Wrapper render=#{render} variants=#{variants.length}>"
|
16
|
+
"#<FuzzyMatch::Wrapper render=#{render.inspect} variants=#{variants.length}>"
|
17
17
|
end
|
18
18
|
|
19
19
|
def read
|
@@ -47,8 +47,10 @@ class FuzzyMatch
|
|
47
47
|
|
48
48
|
alias :to_str :render
|
49
49
|
|
50
|
-
# "Foo's
|
51
|
-
|
50
|
+
# "Foo's" is one word
|
51
|
+
# "North-west" is just one word
|
52
|
+
# "Bolivia," is just Bolivia
|
53
|
+
WORD_BOUNDARY = %r{\W*(?:\s+|$)}
|
52
54
|
def words
|
53
55
|
@words ||= render.downcase.split(WORD_BOUNDARY)
|
54
56
|
end
|
data/test/helper.rb
CHANGED
@@ -4,6 +4,9 @@ Bundler.setup
|
|
4
4
|
require 'test/unit'
|
5
5
|
require 'stringio'
|
6
6
|
require 'remote_table'
|
7
|
+
if ENV['AMATCH'] == 'true'
|
8
|
+
require 'amatch'
|
9
|
+
end
|
7
10
|
$LOAD_PATH.unshift(File.dirname(__FILE__))
|
8
11
|
$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
|
9
12
|
require 'fuzzy_match'
|
data/test/test_fuzzy_match.rb
CHANGED
@@ -156,8 +156,12 @@ class TestFuzzyMatch < Test::Unit::TestCase
|
|
156
156
|
assert_equal nil, d.find('RITZ')
|
157
157
|
|
158
158
|
d = FuzzyMatch.new ["Foo's Bar"], :must_match_at_least_one_word => true
|
159
|
-
assert_equal nil, d.find("Jacob's")
|
160
159
|
assert_equal "Foo's Bar", d.find("Foo's")
|
160
|
+
assert_equal nil, d.find("'s")
|
161
|
+
assert_equal nil, d.find("Foo")
|
162
|
+
|
163
|
+
d = FuzzyMatch.new ["Bolivia, Plurinational State of"], :must_match_at_least_one_word => true
|
164
|
+
assert_equal "Bolivia, Plurinational State of", d.find("Bolivia")
|
161
165
|
end
|
162
166
|
|
163
167
|
def test_020_stop_words
|
@@ -0,0 +1,29 @@
|
|
1
|
+
require 'helper'
|
2
|
+
|
3
|
+
class TestWrapper < Test::Unit::TestCase
|
4
|
+
def test_001_apostrophe_s_is_not_a_word
|
5
|
+
assert_split ["foo's", "bar"], "Foo's Bar"
|
6
|
+
end
|
7
|
+
|
8
|
+
def test_002_bolivia_comma_is_just_bolivia
|
9
|
+
assert_split ["bolivia", "plurinational", "state"], "Bolivia, Plurinational State"
|
10
|
+
end
|
11
|
+
|
12
|
+
def test_003_hyphenated_words_are_not_split_up
|
13
|
+
assert_split ['north-west'], "north-west"
|
14
|
+
end
|
15
|
+
|
16
|
+
def test_004_as_expected
|
17
|
+
assert_split ['the', 'quick', "fox's", 'mouth', 'is', 'always', 'full'], "the quick fox's mouth -- is always full."
|
18
|
+
end
|
19
|
+
|
20
|
+
private
|
21
|
+
|
22
|
+
def assert_split(ary, str)
|
23
|
+
assert_equal ary, FuzzyMatch::Wrapper.new(null_fuzzy_match, str, true).words
|
24
|
+
end
|
25
|
+
|
26
|
+
def null_fuzzy_match
|
27
|
+
FuzzyMatch.new []
|
28
|
+
end
|
29
|
+
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: fuzzy_match
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.2.
|
4
|
+
version: 1.2.2
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,11 +9,11 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2012-01-
|
12
|
+
date: 2012-01-27 00:00:00.000000000Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: shoulda
|
16
|
-
requirement: &
|
16
|
+
requirement: &2170570400 !ruby/object:Gem::Requirement
|
17
17
|
none: false
|
18
18
|
requirements:
|
19
19
|
- - ! '>='
|
@@ -21,10 +21,10 @@ dependencies:
|
|
21
21
|
version: '0'
|
22
22
|
type: :development
|
23
23
|
prerelease: false
|
24
|
-
version_requirements: *
|
24
|
+
version_requirements: *2170570400
|
25
25
|
- !ruby/object:Gem::Dependency
|
26
26
|
name: remote_table
|
27
|
-
requirement: &
|
27
|
+
requirement: &2170569000 !ruby/object:Gem::Requirement
|
28
28
|
none: false
|
29
29
|
requirements:
|
30
30
|
- - ! '>='
|
@@ -32,10 +32,10 @@ dependencies:
|
|
32
32
|
version: '0'
|
33
33
|
type: :development
|
34
34
|
prerelease: false
|
35
|
-
version_requirements: *
|
35
|
+
version_requirements: *2170569000
|
36
36
|
- !ruby/object:Gem::Dependency
|
37
37
|
name: activerecord
|
38
|
-
requirement: &
|
38
|
+
requirement: &2170568220 !ruby/object:Gem::Requirement
|
39
39
|
none: false
|
40
40
|
requirements:
|
41
41
|
- - ! '>='
|
@@ -43,10 +43,10 @@ dependencies:
|
|
43
43
|
version: '3'
|
44
44
|
type: :development
|
45
45
|
prerelease: false
|
46
|
-
version_requirements: *
|
46
|
+
version_requirements: *2170568220
|
47
47
|
- !ruby/object:Gem::Dependency
|
48
48
|
name: mysql
|
49
|
-
requirement: &
|
49
|
+
requirement: &2170567580 !ruby/object:Gem::Requirement
|
50
50
|
none: false
|
51
51
|
requirements:
|
52
52
|
- - ! '>='
|
@@ -54,10 +54,10 @@ dependencies:
|
|
54
54
|
version: '0'
|
55
55
|
type: :development
|
56
56
|
prerelease: false
|
57
|
-
version_requirements: *
|
57
|
+
version_requirements: *2170567580
|
58
58
|
- !ruby/object:Gem::Dependency
|
59
59
|
name: cohort_scope
|
60
|
-
requirement: &
|
60
|
+
requirement: &2170566940 !ruby/object:Gem::Requirement
|
61
61
|
none: false
|
62
62
|
requirements:
|
63
63
|
- - ! '>='
|
@@ -65,10 +65,10 @@ dependencies:
|
|
65
65
|
version: '0'
|
66
66
|
type: :development
|
67
67
|
prerelease: false
|
68
|
-
version_requirements: *
|
68
|
+
version_requirements: *2170566940
|
69
69
|
- !ruby/object:Gem::Dependency
|
70
70
|
name: weighted_average
|
71
|
-
requirement: &
|
71
|
+
requirement: &2170566060 !ruby/object:Gem::Requirement
|
72
72
|
none: false
|
73
73
|
requirements:
|
74
74
|
- - ! '>='
|
@@ -76,10 +76,10 @@ dependencies:
|
|
76
76
|
version: '0'
|
77
77
|
type: :development
|
78
78
|
prerelease: false
|
79
|
-
version_requirements: *
|
79
|
+
version_requirements: *2170566060
|
80
80
|
- !ruby/object:Gem::Dependency
|
81
81
|
name: rake
|
82
|
-
requirement: &
|
82
|
+
requirement: &2170558600 !ruby/object:Gem::Requirement
|
83
83
|
none: false
|
84
84
|
requirements:
|
85
85
|
- - ! '>='
|
@@ -87,10 +87,32 @@ dependencies:
|
|
87
87
|
version: '0'
|
88
88
|
type: :development
|
89
89
|
prerelease: false
|
90
|
-
version_requirements: *
|
90
|
+
version_requirements: *2170558600
|
91
|
+
- !ruby/object:Gem::Dependency
|
92
|
+
name: yard
|
93
|
+
requirement: &2170556920 !ruby/object:Gem::Requirement
|
94
|
+
none: false
|
95
|
+
requirements:
|
96
|
+
- - ! '>='
|
97
|
+
- !ruby/object:Gem::Version
|
98
|
+
version: '0'
|
99
|
+
type: :development
|
100
|
+
prerelease: false
|
101
|
+
version_requirements: *2170556920
|
102
|
+
- !ruby/object:Gem::Dependency
|
103
|
+
name: amatch
|
104
|
+
requirement: &2170555740 !ruby/object:Gem::Requirement
|
105
|
+
none: false
|
106
|
+
requirements:
|
107
|
+
- - ! '>='
|
108
|
+
- !ruby/object:Gem::Version
|
109
|
+
version: '0'
|
110
|
+
type: :development
|
111
|
+
prerelease: false
|
112
|
+
version_requirements: *2170555740
|
91
113
|
- !ruby/object:Gem::Dependency
|
92
114
|
name: activesupport
|
93
|
-
requirement: &
|
115
|
+
requirement: &2170555200 !ruby/object:Gem::Requirement
|
94
116
|
none: false
|
95
117
|
requirements:
|
96
118
|
- - ! '>='
|
@@ -98,10 +120,10 @@ dependencies:
|
|
98
120
|
version: '3'
|
99
121
|
type: :runtime
|
100
122
|
prerelease: false
|
101
|
-
version_requirements: *
|
123
|
+
version_requirements: *2170555200
|
102
124
|
- !ruby/object:Gem::Dependency
|
103
125
|
name: to_regexp
|
104
|
-
requirement: &
|
126
|
+
requirement: &2170554500 !ruby/object:Gem::Requirement
|
105
127
|
none: false
|
106
128
|
requirements:
|
107
129
|
- - ! '>='
|
@@ -109,7 +131,7 @@ dependencies:
|
|
109
131
|
version: 0.0.3
|
110
132
|
type: :runtime
|
111
133
|
prerelease: false
|
112
|
-
version_requirements: *
|
134
|
+
version_requirements: *2170554500
|
113
135
|
description: Find a needle in a haystack using string similarity and (optionally)
|
114
136
|
regexp rules. Replaces loose_tight_dictionary.
|
115
137
|
email:
|
@@ -162,6 +184,7 @@ files:
|
|
162
184
|
- test/test_fuzzy_match_convoluted.rb.disabled
|
163
185
|
- test/test_identity.rb
|
164
186
|
- test/test_normalizer.rb
|
187
|
+
- test/test_wrapper.rb
|
165
188
|
homepage: https://github.com/seamusabshere/fuzzy_match
|
166
189
|
licenses: []
|
167
190
|
post_install_message:
|
@@ -195,4 +218,5 @@ test_files:
|
|
195
218
|
- test/test_fuzzy_match_convoluted.rb.disabled
|
196
219
|
- test/test_identity.rb
|
197
220
|
- test/test_normalizer.rb
|
221
|
+
- test/test_wrapper.rb
|
198
222
|
has_rdoc:
|