autocompletion 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/README.markdown +51 -0
- data/Rakefile +10 -0
- data/autocompletion.gemspec +37 -0
- data/lib/autocompletion.rb +204 -0
- data/lib/autocompletion/version.rb +13 -0
- metadata +49 -0
data/README.markdown
ADDED
@@ -0,0 +1,51 @@
|
|
1
|
+
README
|
2
|
+
======
|
3
|
+
|
4
|
+
|
5
|
+
Summary
|
6
|
+
-------
|
7
|
+
This gem provides fast prefix-autocompletion in pure ruby.
|
8
|
+
|
9
|
+
|
10
|
+
Installation
|
11
|
+
------------
|
12
|
+
`gem install autocompletion`
|
13
|
+
|
14
|
+
|
15
|
+
Usage
|
16
|
+
-----
|
17
|
+
|
18
|
+
### Autocomplete words
|
19
|
+
auto = AutoCompletion.words(%w[foo bar baz])
|
20
|
+
auto.complete('f') # => ["foo"]
|
21
|
+
auto.complete('b') # => ["bar", "baz"]
|
22
|
+
auto.complete('z') # => []
|
23
|
+
|
24
|
+
### Autocomplete objects by attributes
|
25
|
+
Person = Struct.new(:first_name, :last_name)
|
26
|
+
people = [
|
27
|
+
Person.new("Peter", "Parker"),
|
28
|
+
Person.new("Luke", "Skywalker"),
|
29
|
+
Person.new("Anakin", "Skywalker"),
|
30
|
+
]
|
31
|
+
auto = AutoCompletion.map(people) { |person|
|
32
|
+
[person.first_name, person.last_name]
|
33
|
+
}
|
34
|
+
|
35
|
+
auto.complete("P")
|
36
|
+
# => [#<struct Person first_name="Peter", last_name="Parker">]
|
37
|
+
|
38
|
+
auto.complete("S")
|
39
|
+
# => [#<struct Person first_name="Luke", last_name="Skywalker">,
|
40
|
+
# #<struct Person first_name="Anakin", last_name="Skywalker">]
|
41
|
+
|
42
|
+
auto.complete("S", "L")
|
43
|
+
# => [#<struct Person first_name="Luke", last_name="Skywalker">]
|
44
|
+
|
45
|
+
|
46
|
+
Links
|
47
|
+
-----
|
48
|
+
|
49
|
+
* __Github__ http://github.com/apeiros/autocompletion
|
50
|
+
* __Documentation__ http://rdoc.info/github/apeiros/autocompletion/master/frames
|
51
|
+
* __Rubygems__ http://rubygems.org/gems/autocompletion
|
data/Rakefile
ADDED
@@ -0,0 +1,10 @@
|
|
1
|
+
$LOAD_PATH.unshift(File.expand_path('../rake/lib', __FILE__))
|
2
|
+
Dir.glob(File.expand_path('../rake/tasks/**/*.{rake,task,rb}', __FILE__)) do |task_file|
|
3
|
+
begin
|
4
|
+
import task_file
|
5
|
+
rescue LoadError => e
|
6
|
+
warn "Failed to load task file #{task_file}"
|
7
|
+
warn " #{e.class} #{e.message}"
|
8
|
+
warn " #{e.backtrace.first}"
|
9
|
+
end
|
10
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
Gem::Specification.new do |s|
|
4
|
+
s.name = "autocompletion"
|
5
|
+
s.version = "0.0.1"
|
6
|
+
s.authors = "Stefan Rusterholz"
|
7
|
+
s.email = "stefan.rusterholz@gmail.com"
|
8
|
+
s.homepage = "http://github.com/apeiros/autocompletion"
|
9
|
+
|
10
|
+
s.description = <<-DESCRIPTION.gsub(/^ /, '').chomp
|
11
|
+
This gem provides fast prefix-autocompletion in pure ruby.
|
12
|
+
DESCRIPTION
|
13
|
+
|
14
|
+
s.summary = <<-SUMMARY.gsub(/^ /, '').chomp
|
15
|
+
Fast prefix-autocompletion in pure ruby.
|
16
|
+
SUMMARY
|
17
|
+
|
18
|
+
s.files =
|
19
|
+
Dir['bin/**/*'] +
|
20
|
+
Dir['lib/**/*'] +
|
21
|
+
Dir['rake/**/*'] +
|
22
|
+
Dir['test/**/*'] +
|
23
|
+
%w[
|
24
|
+
autocompletion.gemspec
|
25
|
+
Rakefile
|
26
|
+
README.markdown
|
27
|
+
]
|
28
|
+
|
29
|
+
if File.directory?('bin') then
|
30
|
+
executables = Dir.chdir('bin') { Dir.glob('**/*').select { |f| File.executable?(f) } }
|
31
|
+
s.executables = executables unless executables.empty?
|
32
|
+
end
|
33
|
+
|
34
|
+
s.required_rubygems_version = Gem::Requirement.new("> 1.3.1")
|
35
|
+
s.rubygems_version = "1.3.1"
|
36
|
+
s.specification_version = 3
|
37
|
+
end
|
@@ -0,0 +1,204 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
|
4
|
+
|
5
|
+
require 'autocompletion/version'
|
6
|
+
|
7
|
+
|
8
|
+
|
9
|
+
# AutoCompletion
|
10
|
+
# A binary search for prefix-matching is used to determine left- and right-
|
11
|
+
# boundary. This means even with 1_000_000 items, a maximum of 40 comparisons
|
12
|
+
# is required.
|
13
|
+
#
|
14
|
+
# @example Autocomplete words
|
15
|
+
# auto = AutoCompletion.words(%w[foo bar baz])
|
16
|
+
# auto.complete('f') # => ["foo"]
|
17
|
+
# auto.complete('b') # => ["bar", "baz"]
|
18
|
+
# auto.complete('z') # => []
|
19
|
+
#
|
20
|
+
# @example Autocomplete objects by attributes
|
21
|
+
# Person = Struct.new(:first_name, :last_name)
|
22
|
+
# people = [
|
23
|
+
# Person.new("Peter", "Parker"),
|
24
|
+
# Person.new("Luke", "Skywalker"),
|
25
|
+
# Person.new("Anakin", "Skywalker"),
|
26
|
+
# ]
|
27
|
+
# auto = AutoCompletion.map(people) { |person|
|
28
|
+
# [person.first_name, person.last_name]
|
29
|
+
# }
|
30
|
+
#
|
31
|
+
# auto.complete("P")
|
32
|
+
# # => [#<struct Person first_name="Peter", last_name="Parker">]
|
33
|
+
#
|
34
|
+
# auto.complete("S")
|
35
|
+
# # => [#<struct Person first_name="Luke", last_name="Skywalker">,
|
36
|
+
# # #<struct Person first_name="Anakin", last_name="Skywalker">]
|
37
|
+
#
|
38
|
+
# auto.complete("S", "L")
|
39
|
+
# # => [#<struct Person first_name="Luke", last_name="Skywalker">]
|
40
|
+
class AutoCompletion
|
41
|
+
|
42
|
+
# Raised by AutoCompletion::new
|
43
|
+
class InvalidOrder < ArgumentError
|
44
|
+
def initialize
|
45
|
+
super("The prefixes are not in sorted order")
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
# @return [AutoCompletion]
|
50
|
+
# An autocompletion for a list of words.
|
51
|
+
def self.words(words)
|
52
|
+
unordered_tuples(words.map { |word| [word, word] })
|
53
|
+
end
|
54
|
+
|
55
|
+
# @return [AutoCompletion]
|
56
|
+
# Map a list of entities to their keys. The block should return an array of valid
|
57
|
+
# prefixes for the yielded entity.
|
58
|
+
def self.map(entities)
|
59
|
+
mapped = entities.flat_map { |entity|
|
60
|
+
keys = yield(entity)
|
61
|
+
keys.flat_map { |key| [key, entity] }
|
62
|
+
}
|
63
|
+
|
64
|
+
unordered_tuples(mapped.each_slice(2))
|
65
|
+
end
|
66
|
+
|
67
|
+
# @return [AutoCompletion]
|
68
|
+
# Creates an AutoCompletion for an unordered array of the form [["prefix", value], …].
|
69
|
+
def self.unordered_tuples(entities)
|
70
|
+
new(entities.sort_by(&:first).flatten(1), true)
|
71
|
+
end
|
72
|
+
|
73
|
+
# All stored entities. A flat array of the form [prefix1, value1, prefix2, value2, …]
|
74
|
+
attr_reader :entities
|
75
|
+
|
76
|
+
# @param [Array<Array>] entities
|
77
|
+
# A flat array of the form [prefix1, value1, prefix2, value2, …] containing all
|
78
|
+
# prefixes and their corresponding value.
|
79
|
+
# @param [Boolean] force
|
80
|
+
# If force is set to true, the order of entities is not verified. Use this only if
|
81
|
+
# you know what you're doing.
|
82
|
+
#
|
83
|
+
# @see AutoCompletion::words, AutoCompletion::map
|
84
|
+
def initialize(entities, force=false)
|
85
|
+
@entities = entities
|
86
|
+
raise InvalidOrder.new unless force || valid?
|
87
|
+
end
|
88
|
+
|
89
|
+
# @return [Boolean]
|
90
|
+
# Returns true if the prefixes are in a valid order.
|
91
|
+
def valid?
|
92
|
+
@entities.each_slice(2).each_cons(2) do |(a,_),(b,_)|
|
93
|
+
return false unless a <= b
|
94
|
+
end
|
95
|
+
true
|
96
|
+
end
|
97
|
+
|
98
|
+
# @return [Boolean]
|
99
|
+
# Returns true if there are no prefixes stored in this AutoCompletion instance.
|
100
|
+
def empty?
|
101
|
+
@entities.empty?
|
102
|
+
end
|
103
|
+
|
104
|
+
# @return [Integer]
|
105
|
+
# The number of prefixes stored. Note that the same prefix can occur multiple times.
|
106
|
+
def size
|
107
|
+
@entities.size>>1
|
108
|
+
end
|
109
|
+
|
110
|
+
# @return [Integer] The number of distinct entities
|
111
|
+
def count_distinct_entitites
|
112
|
+
result = {}
|
113
|
+
@entities.each_slice(2) do |key, value|
|
114
|
+
result[value] = true
|
115
|
+
end
|
116
|
+
|
117
|
+
result.size
|
118
|
+
end
|
119
|
+
|
120
|
+
# @return [Integer] The number of distinct prefixes
|
121
|
+
def count_distinct_prefixes
|
122
|
+
result = {}
|
123
|
+
@entities.each_slice(2) do |key, value|
|
124
|
+
result[key] = true
|
125
|
+
end
|
126
|
+
|
127
|
+
result.size
|
128
|
+
end
|
129
|
+
|
130
|
+
# @param [String] prefixes
|
131
|
+
# A list of prefixes to match. All given prefixes must be matched.
|
132
|
+
#
|
133
|
+
# @return [Array]
|
134
|
+
# Returns an array of distinct entities matching the given prefixes.
|
135
|
+
def complete(*prefixes)
|
136
|
+
# short-cut
|
137
|
+
return [] if empty? || prefixes.any? { |word|
|
138
|
+
word < @entities.first[0,word.size] || word > @entities[-2][0,word.size]
|
139
|
+
}
|
140
|
+
|
141
|
+
slices = prefixes.map { |word| range_search(word) }
|
142
|
+
return [] if slices.include?(nil) # short-cut
|
143
|
+
|
144
|
+
result = @entities[slices.pop].each_slice(2).map(&:last).uniq
|
145
|
+
slices.each do |slice|
|
146
|
+
result &= @entities[slice].each_slice(2).map(&:last)
|
147
|
+
end
|
148
|
+
|
149
|
+
result
|
150
|
+
end
|
151
|
+
|
152
|
+
# @return [nil, Range<Integer>]
|
153
|
+
# Returns nil if AutoCompletion#empty?
|
154
|
+
# Returns -1..-1 if the prefix is smaller than the smallest key
|
155
|
+
# Returns AutoCompletion#size..AutoCompletion#size if the prefix is bigger than the biggest key
|
156
|
+
# Returns the range for all matched keys and values otherwise
|
157
|
+
def range_search(prefix)
|
158
|
+
prefix_size = prefix.size
|
159
|
+
length = size()
|
160
|
+
found = nil
|
161
|
+
left = 0
|
162
|
+
right = length-1
|
163
|
+
found = false
|
164
|
+
max_exc_right = length
|
165
|
+
|
166
|
+
return nil if empty?
|
167
|
+
return -1..-1 if @entities[0][0,prefix_size] > prefix # prefix is smaller than smallest value
|
168
|
+
return length..length if @entities[-2][0,prefix_size] < prefix # prefix is bigger than biggest value
|
169
|
+
|
170
|
+
# binary search for smallest index
|
171
|
+
# mark biggest right that include prefix, and biggest mark that doesn't include prefix
|
172
|
+
while(left<right)
|
173
|
+
index = (left+right)>>1
|
174
|
+
cmp_value = @entities.at(index<<1)[0,prefix_size]
|
175
|
+
case cmp_value <=> prefix
|
176
|
+
when -1 then
|
177
|
+
left = index+1
|
178
|
+
when 1 then
|
179
|
+
right = index
|
180
|
+
max_exc_right = right
|
181
|
+
else # 0
|
182
|
+
right = index
|
183
|
+
end
|
184
|
+
end
|
185
|
+
return nil unless @entities.at(left<<1)[0,prefix_size] == prefix
|
186
|
+
final_left = left
|
187
|
+
|
188
|
+
# binary search for biggest index
|
189
|
+
right = max_exc_right-1
|
190
|
+
while(left<right)
|
191
|
+
index = (left+right)>>1
|
192
|
+
cmp_value = @entities.at(index<<1)[0,prefix_size]
|
193
|
+
if cmp_value > prefix then
|
194
|
+
right = index
|
195
|
+
else
|
196
|
+
left = index+1
|
197
|
+
end
|
198
|
+
end
|
199
|
+
final_right = right
|
200
|
+
final_right -= 1 unless @entities.at(right<<1)[0,prefix_size] == prefix
|
201
|
+
|
202
|
+
return (final_left<<1)..((final_right<<1)+1)
|
203
|
+
end
|
204
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
begin
|
4
|
+
require 'rubygems/version' # newer rubygems use this
|
5
|
+
rescue LoadError
|
6
|
+
require 'gem/version' # older rubygems use this
|
7
|
+
end
|
8
|
+
|
9
|
+
class AutoCompletion
|
10
|
+
|
11
|
+
# The version of the gem
|
12
|
+
Version = Gem::Version.new("0.0.1")
|
13
|
+
end
|
metadata
ADDED
@@ -0,0 +1,49 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: autocompletion
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Stefan Rusterholz
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2012-02-19 00:00:00.000000000Z
|
13
|
+
dependencies: []
|
14
|
+
description: This gem provides fast prefix-autocompletion in pure ruby.
|
15
|
+
email: stefan.rusterholz@gmail.com
|
16
|
+
executables: []
|
17
|
+
extensions: []
|
18
|
+
extra_rdoc_files: []
|
19
|
+
files:
|
20
|
+
- lib/autocompletion/version.rb
|
21
|
+
- lib/autocompletion.rb
|
22
|
+
- autocompletion.gemspec
|
23
|
+
- Rakefile
|
24
|
+
- README.markdown
|
25
|
+
homepage: http://github.com/apeiros/autocompletion
|
26
|
+
licenses: []
|
27
|
+
post_install_message:
|
28
|
+
rdoc_options: []
|
29
|
+
require_paths:
|
30
|
+
- lib
|
31
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
32
|
+
none: false
|
33
|
+
requirements:
|
34
|
+
- - ! '>='
|
35
|
+
- !ruby/object:Gem::Version
|
36
|
+
version: '0'
|
37
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
38
|
+
none: false
|
39
|
+
requirements:
|
40
|
+
- - ! '>'
|
41
|
+
- !ruby/object:Gem::Version
|
42
|
+
version: 1.3.1
|
43
|
+
requirements: []
|
44
|
+
rubyforge_project:
|
45
|
+
rubygems_version: 1.8.15
|
46
|
+
signing_key:
|
47
|
+
specification_version: 3
|
48
|
+
summary: Fast prefix-autocompletion in pure ruby.
|
49
|
+
test_files: []
|