Class: AutoCompletion
- Inherits:
-
Object
- Object
- AutoCompletion
- Defined in:
- lib/autocompletion.rb,
lib/autocompletion/version.rb
Overview
AutoCompletion A binary search for prefix-matching is used to determine left- and right- boundary. This means even with 1_000_000 items, a maximum of 40 comparisons is required.
Defined Under Namespace
Classes: InvalidOrder
Constant Summary collapse
- Version =
The version of the gem
Gem::Version.new("0.0.3")
Instance Attribute Summary collapse
-
#entities ⇒ Object
readonly
All stored entities.
Class Method Summary collapse
-
.map_key(entities) ⇒ AutoCompletion
Map a list of entities to one of its attributes.
-
.map_keys(entities) ⇒ AutoCompletion
Map a list of entities to many of their attributes.
-
.unordered_tuples(entities) ⇒ AutoCompletion
Creates an AutoCompletion for an unordered array of the form [[“prefix”, value], …].
-
.words(words) ⇒ AutoCompletion
An autocompletion for a list of words.
Instance Method Summary collapse
-
#complete(*prefixes) ⇒ Array
Returns an array of distinct entities matching the given prefixes.
-
#count_distinct_entitites ⇒ Integer
The number of distinct entities.
-
#count_distinct_prefixes ⇒ Integer
The number of distinct prefixes.
-
#empty? ⇒ Boolean
Returns true if there are no prefixes stored in this AutoCompletion instance.
-
#initialize(entities, force = false) ⇒ AutoCompletion
constructor
A new instance of AutoCompletion.
- #inspect ⇒ Object
-
#range_search(prefix) ⇒ nil, Range<Integer>
Returns nil if AutoCompletion#empty? Returns -1..-1 if the prefix is smaller than the smallest key Returns AutoCompletion#size..AutoCompletion#size if the prefix is bigger than the biggest key Returns the range for all matched keys and values otherwise.
-
#size ⇒ Integer
The number of prefixes stored.
-
#valid? ⇒ Boolean
Returns true if the prefixes are in a valid order.
Constructor Details
#initialize(entities, force = false) ⇒ AutoCompletion
Returns a new instance of AutoCompletion.
95 96 97 98 |
# File 'lib/autocompletion.rb', line 95 def initialize(entities, force=false) @entities = entities raise InvalidOrder unless force || valid? end |
Instance Attribute Details
#entities ⇒ Object (readonly)
All stored entities. A flat array of the form [prefix1, value1, prefix2, value2, …]
85 86 87 |
# File 'lib/autocompletion.rb', line 85 def entities @entities end |
Class Method Details
.map_key(entities) ⇒ AutoCompletion
Returns Map a list of entities to one of its attributes. The block should return string which can be prefix-searched.
70 71 72 73 74 75 76 |
# File 'lib/autocompletion.rb', line 70 def self.map_key(entities) mapped = entities.map { |entity| [yield(entity), entity] } unordered_tuples(mapped) end |
.map_keys(entities) ⇒ AutoCompletion
Returns Map a list of entities to many of their attributes. The block should return an array of strings which can be prefix-searched.
58 59 60 61 62 63 64 65 |
# File 'lib/autocompletion.rb', line 58 def self.map_keys(entities) mapped = entities.flat_map { |entity| keys = yield(entity) keys.flat_map { |key| [key, entity] } } unordered_tuples(mapped.each_slice(2)) end |
.unordered_tuples(entities) ⇒ AutoCompletion
Returns Creates an AutoCompletion for an unordered array of the form [[“prefix”, value], …].
80 81 82 |
# File 'lib/autocompletion.rb', line 80 def self.unordered_tuples(entities) new(entities.sort_by(&:first).flatten(1), true) end |
.words(words) ⇒ AutoCompletion
Returns An autocompletion for a list of words.
51 52 53 |
# File 'lib/autocompletion.rb', line 51 def self.words(words) unordered_tuples(words.map { |word| [word, word] }) end |
Instance Method Details
#complete(*prefixes) ⇒ Array
Returns an array of distinct entities matching the given prefixes.
145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 |
# File 'lib/autocompletion.rb', line 145 def complete(*prefixes) # short-cut return [] if empty? || prefixes.empty? || prefixes.any? { |word| word < @entities.first[0,word.size] || word > @entities[-2][0,word.size] } slices = prefixes.map { |word| slice = range_search(word) return [] unless slice # short-cut slice } result = @entities[slices.pop].each_slice(2).map(&:last).uniq slices.each do |slice| result &= @entities[slice].each_slice(2).map(&:last) end result end |
#count_distinct_entitites ⇒ Integer
Returns The number of distinct entities.
121 122 123 124 125 126 127 128 |
# File 'lib/autocompletion.rb', line 121 def count_distinct_entitites result = {} @entities.each_slice(2) do |key, value| result[value] = true end result.size end |
#count_distinct_prefixes ⇒ Integer
Returns The number of distinct prefixes.
131 132 133 134 135 136 137 138 |
# File 'lib/autocompletion.rb', line 131 def count_distinct_prefixes result = {} @entities.each_slice(2) do |key, value| result[key] = true end result.size end |
#empty? ⇒ Boolean
Returns true if there are no prefixes stored in this AutoCompletion instance.
110 111 112 |
# File 'lib/autocompletion.rb', line 110 def empty? @entities.empty? end |
#inspect ⇒ Object
221 222 223 224 225 226 227 228 229 |
# File 'lib/autocompletion.rb', line 221 def inspect sprintf "\#<%s:%x size: %d, unique: %d, %p..%p>", self.class, object_id>>1, size, count_distinct_prefixes, @entities[0], @entities[-2] end |
#range_search(prefix) ⇒ nil, Range<Integer>
Returns nil if AutoCompletion#empty? Returns -1..-1 if the prefix is smaller than the smallest key Returns AutoCompletion#size..AutoCompletion#size if the prefix is bigger than the biggest key Returns the range for all matched keys and values otherwise
171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 |
# File 'lib/autocompletion.rb', line 171 def range_search(prefix) prefix_size = prefix.size length = size() found = nil left = 0 right = length-1 found = false max_exc_right = length return nil if empty? return -1..-1 if @entities[0][0,prefix_size] > prefix # prefix is smaller than smallest value return length..length if @entities[-2][0,prefix_size] < prefix # prefix is bigger than biggest value # binary search for smallest index # mark biggest right which includes prefix, and biggest mark that doesn't include prefix while(left<right) index = (left+right)>>1 cmp_value = @entities.at(index<<1)[0,prefix_size] case cmp_value <=> prefix when -1 then left = index+1 when 1 then right = index max_exc_right = right else # 0 right = index end end return nil unless @entities.at(left<<1)[0,prefix_size] == prefix final_left = left # binary search for biggest index right = max_exc_right-1 while(left<right) index = (left+right)>>1 cmp_value = @entities.at(index<<1)[0,prefix_size] if cmp_value > prefix then right = index else left = index+1 end end final_right = right final_right -= 1 unless @entities.at(right<<1)[0,prefix_size] == prefix return (final_left<<1)..((final_right<<1)+1) end |
#size ⇒ Integer
Returns The number of prefixes stored. Note that the same prefix can occur multiple times.
116 117 118 |
# File 'lib/autocompletion.rb', line 116 def size @entities.size>>1 end |
#valid? ⇒ Boolean
Returns true if the prefixes are in a valid order.
102 103 104 105 106 |
# File 'lib/autocompletion.rb', line 102 def valid? @entities.each_slice(2).each_cons(2).all? { |(a,_),(b,_)| a <= b } end |