ruby-dictionary 1.0.0

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.
@@ -0,0 +1,18 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .idea
6
+ .yardoc
7
+ Gemfile.lock
8
+ InstalledFiles
9
+ _yardoc
10
+ coverage
11
+ doc/
12
+ lib/bundler/man
13
+ pkg
14
+ rdoc
15
+ spec/reports
16
+ test/tmp
17
+ test/version_tmp
18
+ tmp
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in ruby-dictionary.gemspec
4
+ gemspec
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2013 Matt Huggins
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,59 @@
1
+ # Dictionary
2
+
3
+ [ruby-dictionary](https://github.com/mhuggins/ruby-dictionary] provides a
4
+ simple dictionary that allows for checking existence of words and finding a
5
+ subset of words given a prefix.
6
+
7
+ ## Installation
8
+
9
+ Add this line to your application's Gemfile:
10
+
11
+ gem 'ruby-dictionary'
12
+
13
+ And then execute:
14
+
15
+ $ bundle
16
+
17
+ Or install it yourself as:
18
+
19
+ $ gem install ruby-dictionary
20
+
21
+ ## Usage
22
+
23
+ A dictionary is created by passing an array of strings to the initializer.
24
+
25
+ dictionary = Dictionary.new(%w(a ab abs absolute absolutes absolutely
26
+ absolve be bee been bees bend bent best))
27
+
28
+ Alternatively, words can be read in from a file (raw or gzip compressed) as
29
+ well.
30
+
31
+ dictionary = Dictionary.from_file('path/to/uncompressed.txt')
32
+ dictionary = Dictionary.from_file('path/to/compressed.txt.gz')
33
+
34
+ It is assumed that the file contains one word per line. However, a separator
35
+ can be passed to the method as an optional second parameter if that's not the
36
+ case.
37
+
38
+ dictionary = Dictionary.from_file('path/to/uncompressed.txt', ' ')
39
+ dictionary = Dictionary.from_file('path/to/compressed.txt.gz', ',')
40
+
41
+ Once a dictionary is loaded, the `#exists?` method can be used to determine if
42
+ a word exists.
43
+
44
+ dictionary.exists?('bees') # => true
45
+ dictionary.exists?('wasps') # => false
46
+
47
+ The `#starting_with` method returns a sorted array of all words starting with
48
+ the provided string.
49
+
50
+ dictionary.starting_with('bee') # => ["bee", "been", "bees"]
51
+ dictionary.starting_with('foo') # => []
52
+
53
+ ## Contributing
54
+
55
+ 1. Fork it
56
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
57
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
58
+ 4. Push to the branch (`git push origin my-new-feature`)
59
+ 5. Create new Pull Request
@@ -0,0 +1,6 @@
1
+ require 'bundler/gem_tasks'
2
+
3
+ Dir.glob('tasks/**/*.rake').each(&method(:import))
4
+
5
+ desc 'Default: run unit specs'
6
+ task default: :spec
@@ -0,0 +1,2 @@
1
+ require 'ruby-dictionary/dictionary'
2
+ require 'ruby-dictionary/version'
@@ -0,0 +1,73 @@
1
+ # encoding: utf-8
2
+
3
+ require 'zlib'
4
+ require 'ruby-dictionary/word_path'
5
+
6
+ class Dictionary
7
+ def initialize(word_list)
8
+ @word_path = parse_words(word_list)
9
+ end
10
+
11
+ def exists?(word)
12
+ path = word_path(word)
13
+ !!(path && path.leaf?)
14
+ end
15
+
16
+ def starting_with(prefix)
17
+ prefix = prefix.to_s.strip.downcase
18
+ path = word_path(prefix)
19
+ return [] if path.nil?
20
+
21
+ words = [].tap do |words|
22
+ words << prefix if path.leaf?
23
+ words.concat(path.suffixes.collect! { |suffix| "#{prefix}#{suffix}" })
24
+ end
25
+
26
+ words.sort!
27
+ end
28
+
29
+ def hash
30
+ self.class.hash ^ @word_path.hash
31
+ end
32
+
33
+ def ==(obj)
34
+ obj.class == self.class && obj.hash == self.hash
35
+ end
36
+
37
+ def inspect
38
+ "#<#{self.class.name}>"
39
+ end
40
+
41
+ def to_s
42
+ inspect
43
+ end
44
+
45
+ def self.from_file(path, separator = "\n")
46
+ contents = case path
47
+ when String then File.read(path)
48
+ when File then path.read
49
+ else raise ArgumentError, "path must be a String or File"
50
+ end
51
+
52
+ if contents.start_with?("\x1F\x8B")
53
+ gz = Zlib::GzipReader.new(StringIO.new(contents))
54
+ contents = gz.read
55
+ end
56
+
57
+ new(contents.split(separator))
58
+ end
59
+
60
+ private
61
+
62
+ def parse_words(word_list)
63
+ raise ArgumentError, "word_list should be an array of strings" unless word_list.kind_of?(Array)
64
+
65
+ WordPath.new.tap do |word_path|
66
+ word_list.each { |word| word_path << word.to_s }
67
+ end
68
+ end
69
+
70
+ def word_path(str)
71
+ @word_path.find(str)
72
+ end
73
+ end
@@ -0,0 +1,5 @@
1
+ module Ruby
2
+ module Dictionary
3
+ VERSION = '1.0.0'
4
+ end
5
+ end
@@ -0,0 +1,77 @@
1
+ class Dictionary
2
+ class WordPath
3
+ def initialize
4
+ @is_leaf = false
5
+ @word_paths = {}
6
+ end
7
+
8
+ def leaf?
9
+ @is_leaf
10
+ end
11
+
12
+ def leaf=(is_leaf)
13
+ @is_leaf = !!is_leaf
14
+ end
15
+
16
+ def <<(word)
17
+ raise ArgumentError, "must be a string" unless word.kind_of?(String)
18
+ _append(word.strip.downcase)
19
+ end
20
+
21
+ def find(word)
22
+ raise ArgumentError, "must be a string" unless word.kind_of?(String)
23
+ _find(word.strip.downcase)
24
+ end
25
+
26
+ def suffixes
27
+ [].tap do |suffixes|
28
+ @word_paths.each do |letter, path|
29
+ suffixes << letter if path.leaf?
30
+ suffixes.concat(path.suffixes.collect { |suffix| "#{letter}#{suffix}" })
31
+ end
32
+ end
33
+ end
34
+
35
+ def hash
36
+ self.class.hash ^ @is_leaf.hash ^ @word_paths.hash
37
+ end
38
+
39
+ def ==(obj)
40
+ obj.class == self.class && obj.hash == self.hash
41
+ end
42
+
43
+ def inspect
44
+ "#<WordPath @is_leaf=#@is_leaf @word_paths={#{@word_paths.keys.join(',')}}>"
45
+ end
46
+
47
+ def to_s
48
+ inspect
49
+ end
50
+
51
+ protected
52
+
53
+ def _find(word)
54
+ word_path = @word_paths[word[0]]
55
+ return nil unless word_path
56
+
57
+ if word.size == 1
58
+ word_path
59
+ else
60
+ word_path._find(word[1, word.size])
61
+ end
62
+ end
63
+
64
+ def _append(word)
65
+ return if word.empty?
66
+
67
+ char = word[0]
68
+ word_path = @word_paths[char] ||= self.class.new
69
+
70
+ if word.size == 1
71
+ word_path.leaf = true
72
+ else
73
+ word_path._append(word[1, word.size])
74
+ end
75
+ end
76
+ end
77
+ end
@@ -0,0 +1,24 @@
1
+ # -*- encoding: utf-8 -*-
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'ruby-dictionary/version'
5
+
6
+ Gem::Specification.new do |gem|
7
+ gem.name = 'ruby-dictionary'
8
+ gem.version = Ruby::Dictionary::VERSION
9
+ gem.authors = ['Matt Huggins']
10
+ gem.email = ['matt.huggins@gmail.com']
11
+ gem.description = %q{Dictionary class for ruby that allows for checking
12
+ existence and finding words starting with a given
13
+ prefix.}
14
+ gem.summary = 'Simple dictionary class for checking existence of words'
15
+ gem.homepage = 'https://github.com/mhuggins/ruby-dictionary'
16
+
17
+ gem.files = `git ls-files`.split($/)
18
+ gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
19
+ gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
20
+ gem.require_paths = ['lib']
21
+
22
+ gem.add_development_dependency 'rake'
23
+ gem.add_development_dependency 'rspec'
24
+ end
@@ -0,0 +1,16 @@
1
+ a
2
+ ab
3
+ abs
4
+ absolute
5
+ absolutely
6
+
7
+ be
8
+ bee
9
+ bees
10
+ been
11
+
12
+ zoo
13
+ zoos
14
+ zebra
15
+
16
+ yuck
@@ -0,0 +1 @@
1
+ a|ab|abs|absolute|absolutely||be|bee|bees|been||zoo|zoos|zebra||yuck
@@ -0,0 +1,106 @@
1
+ require 'spec_helper'
2
+
3
+ describe Dictionary do
4
+ subject { Dictionary.new(word_list) }
5
+
6
+ let(:word_list) { %w(a ab abs absolute absolutely be bee bees been zoo zoos zebra yuck) }
7
+
8
+ describe '#exists?' do
9
+ it 'finds existing single-letter words' do
10
+ subject.exists?('a').should be_true
11
+ end
12
+
13
+ it 'finds existing multi-letter words' do
14
+ subject.exists?('ab').should be_true
15
+ end
16
+
17
+ it "doesn't find non-existing single-letter words" do
18
+ subject.exists?('e').should be_false
19
+ end
20
+
21
+ it "doesn't find non-existing multi-letter words" do
22
+ subject.exists?('eggsactly').should be_false
23
+ end
24
+ end
25
+
26
+ describe '#starting_with' do
27
+ it 'finds all words starting with a single letter' do
28
+ words = subject.starting_with('a')
29
+ words.size.should eq 5
30
+ words.should include 'a'
31
+ words.should include 'absolutely'
32
+ words.each { |word| word.should start_with 'a' }
33
+ end
34
+
35
+ it 'finds all words starting with multiple letters' do
36
+ words = subject.starting_with('abso')
37
+ words.size.should eq 2
38
+ words.should include 'absolute'
39
+ words.should include 'absolutely'
40
+ words.each { |word| word.should start_with 'a' }
41
+ end
42
+
43
+ it 'finds no words starting with unmatched single letter' do
44
+ subject.starting_with('e').should be_empty
45
+ end
46
+
47
+ it 'finds no words starting with unmatched multiple letters' do
48
+ subject.starting_with('absolutetastic').should be_empty
49
+ end
50
+ end
51
+
52
+ describe '#inspect' do
53
+ specify { subject.inspect.should eq '#<Dictionary>' }
54
+ end
55
+
56
+ describe '#to_s' do
57
+ specify { subject.to_s.should eq '#<Dictionary>' }
58
+ end
59
+
60
+ describe '.from_file' do
61
+ subject { Dictionary.from_file(dictionary_path, separator) }
62
+
63
+ let(:separator) { "\n" }
64
+
65
+ shared_examples 'loaded dictionary' do
66
+ it 'loads all words' do
67
+ subject.starting_with('a').size.should eq 5
68
+ subject.starting_with('b').size.should eq 4
69
+ subject.starting_with('y').size.should eq 1
70
+ subject.starting_with('z').size.should eq 3
71
+ end
72
+
73
+ it 'does not load nonexistent words' do
74
+ ('c'..'x').each do |letter|
75
+ subject.starting_with(letter).should be_empty
76
+ end
77
+ end
78
+ end
79
+
80
+ describe 'with compressed file' do
81
+ describe 'separated by line' do
82
+ let(:dictionary_path) { 'spec/fixtures/compressed_lined.txt.gz' }
83
+ it_behaves_like 'loaded dictionary'
84
+ end
85
+
86
+ describe 'separated by pipe' do
87
+ let(:dictionary_path) { 'spec/fixtures/compressed_piped.txt.gz' }
88
+ let(:separator) { '|' }
89
+ it_behaves_like 'loaded dictionary'
90
+ end
91
+ end
92
+
93
+ describe 'with uncompressed file' do
94
+ describe 'separated by line' do
95
+ let(:dictionary_path) { 'spec/fixtures/uncompressed_lined.txt' }
96
+ it_behaves_like 'loaded dictionary'
97
+ end
98
+
99
+ describe 'separated by pipe' do
100
+ let(:dictionary_path) { 'spec/fixtures/uncompressed_piped.txt' }
101
+ let(:separator) { '|' }
102
+ it_behaves_like 'loaded dictionary'
103
+ end
104
+ end
105
+ end
106
+ end
@@ -0,0 +1,59 @@
1
+ require 'spec_helper'
2
+
3
+ describe Dictionary::WordPath do
4
+ subject { Dictionary::WordPath.new }
5
+
6
+ describe '#leaf=' do
7
+ it 'should set to false' do
8
+ subject.leaf = false
9
+ subject.should_not be_leaf
10
+ end
11
+
12
+ it 'should set to true' do
13
+ subject.leaf = true
14
+ subject.should be_leaf
15
+ end
16
+ end
17
+
18
+ describe '#find' do
19
+ before do
20
+ subject << 'potato'
21
+ end
22
+
23
+ it 'finds existing word paths' do
24
+ word_path = subject.find('potat')
25
+ word_path.should be_a Dictionary::WordPath
26
+ word_path.should eq Dictionary::WordPath.new.tap { |wp| wp << 'o' }
27
+ end
28
+
29
+ it 'does not find nonexistent word paths' do
30
+ subject.find('potable').should be_nil
31
+ end
32
+ end
33
+
34
+ describe '#<<' do
35
+ before do
36
+ subject.find('pot').should be_nil
37
+ end
38
+
39
+ it 'appends word to dictionary' do
40
+ subject << 'potato'
41
+ subject.find('pot').should_not be_nil
42
+ end
43
+ end
44
+
45
+ describe '#suffixes' do
46
+ before do
47
+ subject << 'pot'
48
+ subject << 'potato'
49
+ subject << 'potable'
50
+ subject << 'pert'
51
+ subject << 'jar'
52
+ end
53
+
54
+ it 'finds all words with the given suffix' do
55
+ word = subject.find('pot')
56
+ word.suffixes.should eq %w(ato able)
57
+ end
58
+ end
59
+ end
@@ -0,0 +1 @@
1
+ require 'ruby-dictionary'
@@ -0,0 +1,4 @@
1
+ desc 'Open an irb session preloaded with this library'
2
+ task :console do
3
+ sh 'irb -rubygems -I lib -r ruby-dictionary.rb'
4
+ end
@@ -0,0 +1,6 @@
1
+ require 'rspec/core/rake_task'
2
+
3
+ desc 'Run all examples'
4
+ RSpec::Core::RakeTask.new(:spec) do |t|
5
+ t.rspec_opts = %w(--color)
6
+ end
metadata ADDED
@@ -0,0 +1,94 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: ruby-dictionary
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Matt Huggins
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2013-03-24 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: rake
16
+ requirement: &2157439500 !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: '0'
22
+ type: :development
23
+ prerelease: false
24
+ version_requirements: *2157439500
25
+ - !ruby/object:Gem::Dependency
26
+ name: rspec
27
+ requirement: &2157439080 !ruby/object:Gem::Requirement
28
+ none: false
29
+ requirements:
30
+ - - ! '>='
31
+ - !ruby/object:Gem::Version
32
+ version: '0'
33
+ type: :development
34
+ prerelease: false
35
+ version_requirements: *2157439080
36
+ description: ! "Dictionary class for ruby that allows for checking\n existence
37
+ and finding words starting with a given\n prefix."
38
+ email:
39
+ - matt.huggins@gmail.com
40
+ executables: []
41
+ extensions: []
42
+ extra_rdoc_files: []
43
+ files:
44
+ - .gitignore
45
+ - Gemfile
46
+ - LICENSE.txt
47
+ - README.md
48
+ - Rakefile
49
+ - lib/ruby-dictionary.rb
50
+ - lib/ruby-dictionary/dictionary.rb
51
+ - lib/ruby-dictionary/version.rb
52
+ - lib/ruby-dictionary/word_path.rb
53
+ - ruby-dictionary.gemspec
54
+ - spec/fixtures/compressed_lined.txt.gz
55
+ - spec/fixtures/compressed_piped.txt.gz
56
+ - spec/fixtures/uncompressed_lined.txt
57
+ - spec/fixtures/uncompressed_piped.txt
58
+ - spec/ruby-dictionary/dictionary_spec.rb
59
+ - spec/ruby-dictionary/word_path_spec.rb
60
+ - spec/spec_helper.rb
61
+ - tasks/debug.rake
62
+ - tasks/rspec.rake
63
+ homepage: https://github.com/mhuggins/ruby-dictionary
64
+ licenses: []
65
+ post_install_message:
66
+ rdoc_options: []
67
+ require_paths:
68
+ - lib
69
+ required_ruby_version: !ruby/object:Gem::Requirement
70
+ none: false
71
+ requirements:
72
+ - - ! '>='
73
+ - !ruby/object:Gem::Version
74
+ version: '0'
75
+ required_rubygems_version: !ruby/object:Gem::Requirement
76
+ none: false
77
+ requirements:
78
+ - - ! '>='
79
+ - !ruby/object:Gem::Version
80
+ version: '0'
81
+ requirements: []
82
+ rubyforge_project:
83
+ rubygems_version: 1.8.10
84
+ signing_key:
85
+ specification_version: 3
86
+ summary: Simple dictionary class for checking existence of words
87
+ test_files:
88
+ - spec/fixtures/compressed_lined.txt.gz
89
+ - spec/fixtures/compressed_piped.txt.gz
90
+ - spec/fixtures/uncompressed_lined.txt
91
+ - spec/fixtures/uncompressed_piped.txt
92
+ - spec/ruby-dictionary/dictionary_spec.rb
93
+ - spec/ruby-dictionary/word_path_spec.rb
94
+ - spec/spec_helper.rb