liqrrdmetal 0.5.1 → 0.6

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,2 +1,2 @@
1
- # encoding: UTF-8
1
+ # encoding: UTF-8
2
2
  require_relative 'liqrrdmetal/liqrrdmetal'
@@ -80,7 +80,7 @@ require 'strscan'
80
80
  #
81
81
  # Copyright (c) 2011, Gavin Kistner (!@phrogz.net)
82
82
  module LiqrrdMetal
83
- VERSION = "0.5.1"
83
+ VERSION = "0.6"
84
84
 
85
85
  # If you want score_with_parts to be accurate, the MATCH score must be unique
86
86
  MATCH = 0.00 #:nodoc:
@@ -89,6 +89,7 @@ module LiqrrdMetal
89
89
  BUFFER = [0.15] #:nodoc:
90
90
  TRAILING = [0.20] #:nodoc:
91
91
  NO_MATCH = [1.00] #:nodoc:
92
+ RE_CACHE = {} #:nodoc:
92
93
 
93
94
  # Used to identify substrings and whether or not they were matched directly by the search string.
94
95
  class MatchPart
@@ -147,6 +148,8 @@ module LiqrrdMetal
147
148
  # using the supplied block to find the string to match against,
148
149
  # receiving an array of your objects with the MatchResult module mixed in.
149
150
  #
151
+ # Non-matching entries (score of 1.0) will never be included in the results, no matter the value of score_threshold
152
+ #
150
153
  # User = Struct.new :name, :email, :id
151
154
  # users = [ User.new( "Gavin Kistner", "!@phrogz.net", 42 ),
152
155
  # User.new( "David Letterman", "lateshow@pipeline.com", 17 ),
@@ -163,21 +166,27 @@ module LiqrrdMetal
163
166
  # #=> ["<span class='match'>s</span>cottadams@aol<span class='match'>.com</span>",
164
167
  # #=> "late<span class='match'>s</span>how@pipeline<span class='match'>.com</span>"]
165
168
  def results_by_score( search, objects, score_threshold=1.0 )
166
- objects.each{ |o|
167
- o.extend MatchResult
168
- o.liqrrd_match = yield(o)
169
- o.liqrrd_score, o.liqrrd_parts = score_with_parts(search,o.liqrrd_match)
170
- }.select{ |o|
171
- o.liqrrd_score < score_threshold
172
- }.sort_by{ |o|
169
+ re = RE_CACHE[search] ||= /#{[*search.chars].join('.*?')}/i
170
+ objects.map{ |o|
171
+ m = yield(o)
172
+ if m=~re
173
+ score,parts = score_with_parts(search,m)
174
+ if score<score_threshold
175
+ o.extend MatchResult
176
+ o.liqrrd_match = m
177
+ o.liqrrd_score, o.liqrrd_parts = score,parts
178
+ o
179
+ end
180
+ end
181
+ }.compact.sort_by{ |o|
173
182
  [ o.liqrrd_score, o.liqrrd_match ]
174
183
  }
175
184
  end
176
185
 
177
186
  # Match a single search term against an array of possible results,
178
187
  # receiving an array sorted by score (descending) of the matched text parts.
179
- # By default non-matching entries are not included in the results; set the
180
- # `score_threshold` below 0.0 to include them.
188
+ #
189
+ # Non-matching entries (score of 1.0) will never be included in the results, no matter the value of score_threshold
181
190
  #
182
191
  # items = ["FooBar","Foo Bar","For the Love of Big Cars"]
183
192
  # hits = LiqrrdMetal.parts_by_score( "foobar", items )
@@ -186,11 +195,15 @@ module LiqrrdMetal
186
195
  # #=> _Foo_ _Bar_
187
196
  # #=> _Fo_r the L_o_ve of _B_ig C_ar_s
188
197
  def parts_by_score( search, actuals, score_threshold=1.0 )
198
+ re = RE_CACHE[search] ||= /#{[*search.chars].join('.*?')}/i
189
199
  actuals.map{ |actual|
190
- [ actual, *score_with_parts(search,actual) ]
191
- }.select{ |actual,score,parts|
192
- score < score_threshold
193
- }.sort_by{ |actual,score,parts|
200
+ if actual=~re
201
+ score,parts = score_with_parts(search,actual)
202
+ if score<score_threshold
203
+ [ actual, score, parts ]
204
+ end
205
+ end
206
+ }.compact.sort_by{ |actual,score,parts|
194
207
  [ score, actual ]
195
208
  }.map{ |actual,score,parts|
196
209
  parts
@@ -208,12 +221,13 @@ module LiqrrdMetal
208
221
  # p parts.map(&:to_html).join
209
222
  # #=> "A <span class='match'>Foo</span>l in Lo<span class='match'>v</span>e"
210
223
  def score_with_parts( search, actual )
224
+ re = RE_CACHE[search] ||= /#{[*search.chars].join('.*?')}/i
211
225
  if search.length==0
212
226
  [ TRAILING[0], [MatchPart.new(actual)] ]
213
- elsif search.length > actual.length
227
+ elsif (search.length > actual.length) || (search !~ re)
214
228
  [ NO_MATCH[0], [MatchPart.new(actual)] ]
215
229
  else
216
- values = scores( search, actual )
230
+ values = letter_scores( search, actual )
217
231
  score = values.inject{ |sum,score| sum+score } / values.length
218
232
  was_matching,start = nil
219
233
  parts = []
@@ -229,23 +243,42 @@ module LiqrrdMetal
229
243
  [ score, parts ]
230
244
  end
231
245
  end
246
+
247
+ # Returns an array of score/string tuples, sorted by score, below the <code>score_threshold</code>
248
+ #
249
+ # Non-matching entries (score of 1.0) will never be included in the results, no matter the value of <code>score_threshold</code>
250
+ def sorted_with_scores( search, actuals, score_threshold=1.0 )
251
+ if search.length==0
252
+ []
253
+ else
254
+ re = RE_CACHE[search] ||= /#{[*search.chars].join('.*?')}/i
255
+ actuals.map{ |actual|
256
+ if actual=~re
257
+ values = letter_scores( search, actual )
258
+ score = values.inject{ |sum,score| sum+score } / values.length
259
+ [score,actual] if score < score_threshold
260
+ end
261
+ }.compact.sort
262
+ end
263
+ end
232
264
 
233
265
  # Return a score for matching the search term against the actual text.
234
266
  # A score of <code>1.0</code> indicates no match. A score of <code>0.0</code> is a perfect match.
235
267
  def score( search, actual )
268
+ re = RE_CACHE[search] ||= /#{[*search.chars].join('.*?')}/i
236
269
  if search.length==0
237
270
  TRAILING[0]
238
- elsif search.length > actual.length
271
+ elsif (search.length > actual.length) || (search !~ re)
239
272
  NO_MATCH[0]
240
273
  else
241
- values = scores( search, actual )
274
+ values = letter_scores( search, actual )
242
275
  values.inject{ |sum,score| sum+score } / values.length
243
276
  end
244
277
  end
245
278
 
246
279
  # Return an aray of scores for each letter in the actual text.
247
280
  # Returns a single-value array of <code>[0.0]</code> if no match exists.
248
- def scores( search, actual )
281
+ def letter_scores( search, actual )
249
282
  actual_length = actual.length
250
283
  scores = Array.new(actual_length)
251
284
 
@@ -270,6 +303,4 @@ module LiqrrdMetal
270
303
  scores[ (last+1)...scores.length ] = (started ? TRAILING_BUT_STARTED : TRAILING) * (scores.length-last-1)
271
304
  scores
272
305
  end
273
- end
274
-
275
-
306
+ end
metadata CHANGED
@@ -1,52 +1,64 @@
1
- --- !ruby/object:Gem::Specification
1
+ --- !ruby/object:Gem::Specification
2
2
  name: liqrrdmetal
3
- version: !ruby/object:Gem::Version
4
- version: 0.5.1
5
- prerelease:
3
+ version: !ruby/object:Gem::Version
4
+ prerelease: false
5
+ segments:
6
+ - 0
7
+ - 6
8
+ version: "0.6"
6
9
  platform: ruby
7
- authors:
10
+ authors:
8
11
  - Gavin Kistner
9
12
  autorequire:
10
13
  bindir: bin
11
14
  cert_chain: []
12
- date: 2011-04-19 00:00:00.000000000 -06:00
15
+
16
+ date: 2011-04-19 00:00:00 -06:00
13
17
  default_executable:
14
18
  dependencies: []
15
- description: Derived from the LiquidMetal JavaScript library, LiqrrdMetal brings substring
16
- scoring to Ruby. Similar to Quicksilver, LiqrrdMetal gives users the ability to
17
- quickly find the most relevant items by typing in portions of the string, while
18
- seeing the portions of the substring that are being matched.
19
+
20
+ description: Derived from the LiquidMetal JavaScript library, LiqrrdMetal brings substring scoring to Ruby. Similar to Quicksilver, LiqrrdMetal gives users the ability to quickly find the most relevant items by typing in portions of the string, while seeing the portions of the substring that are being matched.
19
21
  email: gavin@phrogz.net
20
22
  executables: []
23
+
21
24
  extensions: []
25
+
22
26
  extra_rdoc_files: []
23
- files:
27
+
28
+ files:
24
29
  - lib/liqrrdmetal/liqrrdmetal.rb
25
30
  - lib/liqrrdmetal.rb
26
31
  has_rdoc: true
27
32
  homepage: http://github.com/Phrogz/liqrrdmetal
28
33
  licenses: []
34
+
29
35
  post_install_message:
30
36
  rdoc_options: []
31
- require_paths:
37
+
38
+ require_paths:
32
39
  - lib
33
- required_ruby_version: !ruby/object:Gem::Requirement
40
+ required_ruby_version: !ruby/object:Gem::Requirement
34
41
  none: false
35
- requirements:
36
- - - ! '>='
37
- - !ruby/object:Gem::Version
38
- version: '0'
39
- required_rubygems_version: !ruby/object:Gem::Requirement
42
+ requirements:
43
+ - - ">="
44
+ - !ruby/object:Gem::Version
45
+ segments:
46
+ - 0
47
+ version: "0"
48
+ required_rubygems_version: !ruby/object:Gem::Requirement
40
49
  none: false
41
- requirements:
42
- - - ! '>='
43
- - !ruby/object:Gem::Version
44
- version: '0'
45
- requirements:
50
+ requirements:
51
+ - - ">="
52
+ - !ruby/object:Gem::Version
53
+ segments:
54
+ - 0
55
+ version: "0"
56
+ requirements:
46
57
  - StringScanner (part of the Ruby Standard Library)
47
58
  rubyforge_project:
48
- rubygems_version: 1.5.2
59
+ rubygems_version: 1.3.7
49
60
  signing_key:
50
61
  specification_version: 3
51
62
  summary: Calculate scoring of autocomplete-style substring matches.
52
63
  test_files: []
64
+