Module: LiqrrdMetal
- Defined in:
- lib/liqrrdmetal/liqrrdmetal.rb
Overview
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.
To facilitate common sorting, lower scores are better; a score of 0.0 indicates a perfect match, while a score of 1.0 indicates no match.
Usage
Starting with the basics, here is how to find the score for a possible match:
score = LiqqrdMetal.score( "re", "regards.txt" )
#=> 0.082
score = LiqqrdMetal.score( "re", "preview.jpg" )
#=> 0.236
score = LiqqrdMetal.score( "re", "no" )
#=> 1.0
Want to know which letters were matched?
score,parts = LiqqrdMetal.score_with_parts( "re", "Preview.jpg" )
puts "%.02f" % score
#=> 0.24
p parts
#=> [#<struct LiqrrdMetal::MatchPart text="P", match=false>,
#=> #<struct LiqrrdMetal::MatchPart text="re", match=true>,
#=> #<struct LiqrrdMetal::MatchPart text="view.jpg", match=false>]]
puts parts.join
#=> Preview.jpg
puts parts.map(&:to_html).join
#=> P<span class='match'>re</span>view.jpg
require 'json'
puts parts.to_json
#=> [{"t":"P","m":false},{"t":"re","m":true},{"t":"view.jpg","m":false}]
Sort an array of possible matches by score, removing low-scoring items:
def best_matches( search, strings )
strings.map{ |s|
[LiqrrdMetal.score(search,s),s]
}.select{ |score,string|
score < 0.3
}.sort.map{ |score,string|
string
}
end
p best_matches( "re", various_filenames )
#=> ["resizing-text.svg", "PreviewIcon.psd" ]
Given an array of possible matches, return the matching parts sorted by score:
hits = LiqrrdMetal.parts_by_score( "re", various_filenames )
p hits.map(&:join)
#=> ["resizing-text.svg", "PreviewIcon.psd", "prime-finder.rb" ]
p hits.map{ |parts| parts.map(&:to_ascii).join }
#=> ["_re_sizing-text.svg", "P_re_viewIcon.psd", "p_r_im_e_-finder.rb" ]
You can also specify the threshold for the parts_by_score method:
good_hits = LiqrrdMetal.parts_by_score( "re", various_filenames, 0.3 )
License & Contact
LiqrrdMetal is released under the MIT License.
Copyright © 2011, Gavin Kistner ([email protected])
Defined Under Namespace
Classes: MatchPart
Constant Summary collapse
- VERSION =
0.5
- MATCH =
If you want score_with_parts to be accurate, the MATCH score must be unique
0.00
- NEW_WORD =
:nodoc:
0.01
- TRAILING_BUT_STARTED =
:nodoc:
[0.10]
- BUFFER =
:nodoc:
[0.15]
- TRAILING =
:nodoc:
[0.20]
- NO_MATCH =
:nodoc:
[1.00]
Class Method Summary collapse
-
.parts_by_score(search, actuals, score_threshold = 1.0) ⇒ Object
#=> FooBar #=> Foo Bar #=> _Fo_r the L_o_ve of _B_ig C_ar_s.
-
.score(search, actual) ⇒ Object
Return a score for matching the search term against the actual text.
-
.score_with_parts(search, actual) ⇒ Object
Returns an array with the score of the match, followed by an array of MatchPart instances.
-
.scores(search, actual) ⇒ Object
Return an aray of scores for each letter in the actual text.
Class Method Details
.parts_by_score(search, actuals, score_threshold = 1.0) ⇒ Object
#=> FooBar
#=> _Foo_ _Bar_
#=> _Fo_r the L_o_ve of _B_ig C_ar_s
145 146 147 148 149 150 151 152 153 154 155 |
# File 'lib/liqrrdmetal/liqrrdmetal.rb', line 145 def parts_by_score( search, actuals, score_threshold=1.0 ) actuals.map{ |actual| [ actual, *score_with_parts(search,actual) ] }.select{ |actual,score,parts| score < score_threshold }.sort_by{ |actual,score,parts| [ score, actual ] }.map{ |actual,score,parts| parts } end |
.score(search, actual) ⇒ Object
Return a score for matching the search term against the actual text. A score of 1.0
indicates no match. A score of 0.0
is a perfect match.
192 193 194 195 196 197 198 199 200 201 |
# File 'lib/liqrrdmetal/liqrrdmetal.rb', line 192 def score( search, actual ) if search.length==0 TRAILING[0] elsif search.length > actual.length NO_MATCH[0] else values = scores( search, actual ) values.inject{ |sum,score| sum+score } / values.length end end |
.score_with_parts(search, actual) ⇒ Object
Returns an array with the score of the match, followed by an array of MatchPart instances.
score, parts = LiqrrdMetal.score_with_parts( "foov", "A Fool in Love" )
puts "%0.2f" % score
#=> 0.46
p parts.map{ |p| p.match? ? "_#{p}_" : p.text }.join
#=> "A _Foo_l in Lo_v_e"
p parts.map(&:to_html).join
#=> "A <span class='match'>Foo</span>l in Lo<span class='match'>v</span>e"
167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 |
# File 'lib/liqrrdmetal/liqrrdmetal.rb', line 167 def score_with_parts( search, actual ) if search.length==0 [ TRAILING[0], [MatchPart.new(actual)] ] elsif search.length > actual.length [ NO_MATCH[0], [MatchPart.new(actual)] ] else values = scores( search, actual ) score = values.inject{ |sum,score| sum+score } / values.length was_matching,start = nil parts = [] values.each_with_index do |score,i| is_match = score==MATCH if is_match != was_matching parts << MatchPart.new(actual[start...i],was_matching) if start was_matching = is_match start = i end end parts << MatchPart.new(actual[start..-1],was_matching) if start [ score, parts ] end end |
.scores(search, actual) ⇒ Object
Return an aray of scores for each letter in the actual text. Returns a single-value array of [0.0]
if no match exists.
205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 |
# File 'lib/liqrrdmetal/liqrrdmetal.rb', line 205 def scores( search, actual ) actual_length = actual.length scores = Array.new(actual_length) last = -1 started = false scanner = StringScanner.new actual search.chars.each do |c| return NO_MATCH unless fluff = scanner.scan_until(/#{c}/i) pos = scanner.pos-1 started = true if pos == 0 if /\s/ =~ actual[pos-1] scores[pos-1] = NEW_WORD unless pos==0 scores[(last+1)..(pos-1)] = BUFFER*(fluff.length-1) elsif /[A-Z]/ =~ actual[pos] scores[(last+1)..pos] = BUFFER*fluff.length else scores[(last+1)..pos] = NO_MATCH*fluff.length end scores[pos] = MATCH last = pos end scores[ (last+1)...scores.length ] = (started ? TRAILING_BUT_STARTED : TRAILING) * (scores.length-last-1) scores end |