ruby-dictionary 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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