lib/mongoid_fulltext.rb in mongoid_fulltext-0.5.1 vs lib/mongoid_fulltext.rb in mongoid_fulltext-0.5.2

- old
+ new

@@ -1,7 +1,8 @@ require 'mongoid_indexes' require 'unicode_utils' +require 'cgi' module Mongoid::FullTextSearch extend ActiveSupport::Concern included do @@ -178,55 +179,60 @@ else results.map { |result| instantiate_mapreduce_result(result) }.compact end end - # returns an [ngram, score] [ngram, position] pair def all_ngrams(str, config, bound_number_returned = true) - return {} if str.nil? or str.length < config[:ngram_width] + return {} if str.nil? - filtered_str = String.new(str) if config[:remove_accents] - if str.encoding.name == "ASCII-8BIT" - filtered_str = CGI.unescape(filtered_str) - end - filtered_str = UnicodeUtils.nfkd(filtered_str).gsub(/[^\x00-\x7F]/,'') + str = UnicodeUtils.nfkd(CGI.unescape(str)).gsub(/[^\x00-\x7F]/,'') end - filtered_str = filtered_str.mb_chars.downcase.to_s.split('').map{ |ch| config[:alphabet][ch] }.compact.join('') + # Remove any characters that aren't in the alphabet + filtered_str = str.mb_chars.to_s.downcase.split('').find_all{ |ch| config[:alphabet][ch] }.join('') + # Figure out how many ngrams to extract from the string. If we can't afford to extract all ngrams, + # step over the string in evenly spaced strides to extract ngrams. For example, to extract 3 3-letter + # ngrams from 'abcdefghijk', we'd want to extract 'abc', 'efg', and 'ijk'. if bound_number_returned step_size = [((filtered_str.length - config[:ngram_width]).to_f / config[:max_ngrams_to_search]).ceil, 1].max else step_size = 1 end - # Create an array of records of the form {:ngram => x, :score => y} for all ngrams that occur in the input string - ngram_ary = (0..filtered_str.length - config[:ngram_width]).step(step_size).map do |i| + # Create an array of records of the form {:ngram => x, :score => y} for all ngrams that occur in the + # input string using the step size that we just computed. Let score(x,y) be the score of string x + # compared with string y - assigning scores to ngrams with the square root-based scoring function + # below and multiplying scores of matching ngrams together yields a score function that has the + # property that score(x,y) > score(x,z) for any string z containing y and score(x,y) > score(x,z) + # for any string z contained in y. + ngram_array = (0..filtered_str.length - config[:ngram_width]).step(step_size).map do |i| if i == 0 or (config[:apply_prefix_scoring_to_all_words] and \ config[:word_separators].has_key?(filtered_str[i-1].chr)) score = Math.sqrt(1 + 1.0/filtered_str.length) else score = Math.sqrt(2.0/filtered_str.length) end {:ngram => filtered_str[i..i+config[:ngram_width]-1], :score => score} end # If an ngram appears multiple times in the query string, keep the max score - ngram_ary = ngram_ary.group_by{ |h| h[:ngram] }.map{ |key, values| {:ngram => key, :score => values.map{ |v| v[:score] }.max} } + ngram_array = ngram_array.group_by{ |h| h[:ngram] }.map{ |key, values| {:ngram => key, :score => values.map{ |v| v[:score] }.max} } + # Add records to the array of ngrams for each full word in the string that isn't a stop word if (config[:index_full_words]) full_words_seen = {} filtered_str.split(Regexp.compile(config[:word_separators].keys.join)).each do |word| - if word.length >= config[:ngram_width] and full_words_seen[word].nil? and config[:stop_words][word].nil? - ngram_ary << {:ngram => word, :score => 1} + if word.length > 1 and full_words_seen[word].nil? and config[:stop_words][word].nil? + ngram_array << {:ngram => word, :score => 1} full_words_seen[word] = true end end end # If an ngram appears as a full word and an ngram, keep the sum of the two scores - Hash[ngram_ary.group_by{ |h| h[:ngram] }.map{ |key, values| [key, values.map{ |v| v[:score] }.sum] }] + Hash[ngram_array.group_by{ |h| h[:ngram] }.map{ |key, values| [key, values.map{ |v| v[:score] }.sum] }] end def remove_from_ngram_index self.mongoid_fulltext_config.each_pair do |index_name, fulltext_config| coll = collection.db.collection(index_name)