ruby-dictionary 1.0.1 → 1.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/.travis.yml CHANGED
@@ -1,5 +1,10 @@
1
1
  language: ruby
2
+
2
3
  rvm:
3
4
  - 1.9.3
4
5
  - 2.0.0
5
6
  - ruby-head
7
+
8
+ matrix:
9
+ allow_failures:
10
+ - rvm: ruby-head
data/README.md CHANGED
@@ -53,6 +53,32 @@ the provided string.
53
53
  dictionary.starting_with('bee') # => ["bee", "been", "bees"]
54
54
  dictionary.starting_with('foo') # => []
55
55
 
56
+ ### Case Sensitivity
57
+
58
+ By default, a new `Dictionary` is case-insensitive, meaning "bee", "Bee", and
59
+ "BEE" are all considered to be the same, regardless of adding to the dictionary
60
+ or searching within it.
61
+
62
+ However, you can choose to use case-sensitive dictionary by passing an optional
63
+ `true` parameter to both the `#new` and `#from_file` methods.
64
+
65
+ dictionary = Dictionary.new(%w(Alpha Beta), true)
66
+ dictionary.exists?('Alpha') # => true
67
+ dictionary.exists?('alpha') # => false
68
+
69
+ dictionary = Dictionary.from_file('restaurants.txt', "\n", true)
70
+ dictionary.starting_with('Mc') # => ["McDonald's"]
71
+ dictionary.starting_with('mc') # => []
72
+
73
+ Additionally, you can determine whether a dictionary is case-sensitive via the
74
+ `#case_sensitive?` method.
75
+
76
+ dictionary = Dictionary.new([])
77
+ dictionary.case_sensitive? # => false
78
+
79
+ dictionary = Dictionary.new([], true)
80
+ dictionary.case_sensitive? # => true
81
+
56
82
  ## Contributing
57
83
 
58
84
  1. Fork it
@@ -4,8 +4,12 @@ require 'zlib'
4
4
  require 'ruby-dictionary/word_path'
5
5
 
6
6
  class Dictionary
7
- def initialize(word_list)
8
- @word_path = parse_words(word_list)
7
+ def initialize(word_list, case_sensitive = false)
8
+ @word_path = parse_words(word_list, case_sensitive)
9
+ end
10
+
11
+ def case_sensitive?
12
+ @word_path.case_sensitive?
9
13
  end
10
14
 
11
15
  def exists?(word)
@@ -14,7 +18,9 @@ class Dictionary
14
18
  end
15
19
 
16
20
  def starting_with(prefix)
17
- prefix = prefix.to_s.strip.downcase
21
+ prefix = prefix.to_s.strip
22
+ prefix = prefix.downcase unless case_sensitive?
23
+
18
24
  path = word_path(prefix)
19
25
  return [] if path.nil?
20
26
 
@@ -42,11 +48,11 @@ class Dictionary
42
48
  inspect
43
49
  end
44
50
 
45
- def self.from_file(path, separator = "\n")
51
+ def self.from_file(path, separator = "\n", case_sensitive = false)
46
52
  contents = case path
47
53
  when String then File.read(path)
48
54
  when File then path.read
49
- else raise ArgumentError, "path must be a String or File"
55
+ else raise ArgumentError, 'path must be a String or File'
50
56
  end
51
57
 
52
58
  if contents.start_with?("\x1F\x8B")
@@ -54,15 +60,15 @@ class Dictionary
54
60
  contents = gz.read
55
61
  end
56
62
 
57
- new(contents.split(separator))
63
+ new(contents.split(separator), case_sensitive)
58
64
  end
59
65
 
60
66
  private
61
67
 
62
- def parse_words(word_list)
63
- raise ArgumentError, "word_list should be an array of strings" unless word_list.kind_of?(Array)
68
+ def parse_words(word_list, case_sensitive)
69
+ raise ArgumentError, 'word_list should be an array of strings' unless word_list.kind_of?(Array)
64
70
 
65
- WordPath.new.tap do |word_path|
71
+ WordPath.new(case_sensitive).tap do |word_path|
66
72
  word_list.each { |word| word_path << word.to_s }
67
73
  end
68
74
  end
@@ -1,3 +1,3 @@
1
1
  class Dictionary
2
- VERSION = '1.0.1'
2
+ VERSION = '1.1.0'
3
3
  end
@@ -1,10 +1,15 @@
1
1
  class Dictionary
2
2
  class WordPath
3
- def initialize
3
+ def initialize(case_sensitive)
4
+ @case_sensitive = !!case_sensitive
4
5
  @is_leaf = false
5
6
  @word_paths = {}
6
7
  end
7
8
 
9
+ def case_sensitive?
10
+ @case_sensitive
11
+ end
12
+
8
13
  def leaf?
9
14
  @is_leaf
10
15
  end
@@ -14,13 +19,15 @@ class Dictionary
14
19
  end
15
20
 
16
21
  def <<(word)
17
- raise ArgumentError, "must be a string" unless word.kind_of?(String)
18
- _append(word.strip.downcase)
22
+ raise ArgumentError, 'must be a string' unless word.kind_of?(String)
23
+ word = word.downcase unless @case_sensitive
24
+ _append(word.strip)
19
25
  end
20
26
 
21
27
  def find(word)
22
- raise ArgumentError, "must be a string" unless word.kind_of?(String)
23
- _find(word.strip.downcase)
28
+ raise ArgumentError, 'must be a string' unless word.kind_of?(String)
29
+ word = word.downcase unless @case_sensitive
30
+ _find(word.strip)
24
31
  end
25
32
 
26
33
  def suffixes
@@ -33,7 +40,7 @@ class Dictionary
33
40
  end
34
41
 
35
42
  def hash
36
- self.class.hash ^ @is_leaf.hash ^ @word_paths.hash
43
+ self.class.hash ^ @is_leaf.hash ^ @word_paths.hash ^ @case_sensitive.hash
37
44
  end
38
45
 
39
46
  def ==(obj)
@@ -65,7 +72,7 @@ class Dictionary
65
72
  return if word.empty?
66
73
 
67
74
  char = word[0]
68
- word_path = @word_paths[char] ||= self.class.new
75
+ word_path = @word_paths[char] ||= self.class.new(@case_sensitive)
69
76
 
70
77
  if word.size == 1
71
78
  word_path.leaf = true
@@ -1,51 +1,126 @@
1
1
  require 'spec_helper'
2
2
 
3
3
  describe Dictionary do
4
- subject { Dictionary.new(word_list) }
4
+ subject { Dictionary.new(word_list, case_sensitive) }
5
5
 
6
6
  let(:word_list) { %w(a ab abs absolute absolutely be bee bees been zoo zoos zebra yuck) }
7
+ let(:case_sensitive) { false }
7
8
 
8
- describe '#exists?' do
9
- it 'finds existing single-letter words' do
10
- subject.exists?('a').should be_true
9
+ describe '#case_sensitive?' do
10
+ describe 'when case-insensitive' do
11
+ it { should_not be_case_sensitive }
11
12
  end
12
13
 
13
- it 'finds existing multi-letter words' do
14
- subject.exists?('ab').should be_true
14
+ describe 'when case-sensitive' do
15
+ let(:case_sensitive) { true }
16
+
17
+ it { should be_case_sensitive }
15
18
  end
19
+ end
20
+
21
+ describe '#exists?' do
22
+ describe 'when case-insensitive' do
23
+ it 'finds existing single-letter words regardless of casing' do
24
+ subject.exists?('a').should be_true
25
+ subject.exists?('A').should be_true
26
+ end
16
27
 
17
- it "doesn't find non-existing single-letter words" do
18
- subject.exists?('e').should be_false
28
+ it 'finds existing multi-letter words regardless of casing' do
29
+ subject.exists?('ab').should be_true
30
+ subject.exists?('AB').should be_true
31
+ end
32
+
33
+ it "doesn't find non-existing single-letter words regardless of casing" do
34
+ subject.exists?('e').should be_false
35
+ subject.exists?('E').should be_false
36
+ end
37
+
38
+ it "doesn't find non-existing multi-letter words regardless of casing" do
39
+ subject.exists?('eggsactly').should be_false
40
+ subject.exists?('EGGSACTLY').should be_false
41
+ end
19
42
  end
20
43
 
21
- it "doesn't find non-existing multi-letter words" do
22
- subject.exists?('eggsactly').should be_false
44
+ describe 'when case-sensitive' do
45
+ let(:case_sensitive) { true }
46
+
47
+ it 'finds existing single-letter words of the same casing' do
48
+ subject.exists?('a').should be_true
49
+ end
50
+
51
+ it 'finds existing multi-letter words of the same casing' do
52
+ subject.exists?('ab').should be_true
53
+ end
54
+
55
+ it "doesn't find existing single-letter words of a different casing" do
56
+ subject.exists?('A').should be_false
57
+ end
58
+
59
+ it "doesn't find existing multi-letter words of a different casing" do
60
+ subject.exists?('AB').should be_false
61
+ end
62
+
63
+ it "doesn't find non-existing single-letter words of any casing" do
64
+ subject.exists?('e').should be_false
65
+ subject.exists?('E').should be_false
66
+ end
67
+
68
+ it "doesn't find non-existing multi-letter words of any casing" do
69
+ subject.exists?('eggsactly').should be_false
70
+ subject.exists?('EGGSACTLY').should be_false
71
+ end
23
72
  end
24
73
  end
25
74
 
26
75
  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
76
+ describe 'when case-insensitive' do
77
+ it 'finds all words starting with a single letter regardless of casing' do
78
+ %w(a A).each do |prefix|
79
+ subject.starting_with(prefix).should eq %w(a ab abs absolute absolutely)
80
+ end
81
+ end
34
82
 
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
83
+ it 'finds all words starting with multiple letters regardless of casing' do
84
+ %w(abso ABSO).each do |prefix|
85
+ subject.starting_with(prefix).should eq %w(absolute absolutely)
86
+ end
87
+ end
42
88
 
43
- it 'finds no words starting with unmatched single letter' do
44
- subject.starting_with('e').should be_empty
89
+ it 'finds no words starting with unmatched single letter regardless of casing' do
90
+ %w(e E).each do |prefix|
91
+ subject.starting_with(prefix).should be_empty
92
+ end
93
+ end
94
+
95
+ it 'finds no words starting with unmatched multiple letters regardless of casing' do
96
+ %w(absolutetastic ABSOLUTETASTIC).each do |prefix|
97
+ subject.starting_with(prefix).should be_empty
98
+ end
99
+ end
45
100
  end
46
101
 
47
- it 'finds no words starting with unmatched multiple letters' do
48
- subject.starting_with('absolutetastic').should be_empty
102
+ describe 'when case-sensitive' do
103
+ let(:case_sensitive) { true }
104
+
105
+ it 'finds all words starting with a single letter of the same casing' do
106
+ subject.starting_with('a').should eq %w(a ab abs absolute absolutely)
107
+ subject.starting_with('A').should be_empty
108
+ end
109
+
110
+ it 'finds all words starting with multiple letters of the same casing' do
111
+ subject.starting_with('abso').should eq %w(absolute absolutely)
112
+ subject.starting_with('ABSO').should be_empty
113
+ end
114
+
115
+ it 'finds no words starting with unmatched single letter of the same casing' do
116
+ subject.starting_with('e').should be_empty
117
+ subject.starting_with('E').should be_empty
118
+ end
119
+
120
+ it 'finds no words starting with unmatched multiple letters of the same casing' do
121
+ subject.starting_with('absolutetastic').should be_empty
122
+ subject.starting_with('ABSOLUTETASTIC').should be_empty
123
+ end
49
124
  end
50
125
  end
51
126
 
@@ -58,7 +133,7 @@ describe Dictionary do
58
133
  end
59
134
 
60
135
  describe '.from_file' do
61
- subject { Dictionary.from_file(dictionary_path, separator) }
136
+ subject { Dictionary.from_file(dictionary_path, separator, case_sensitive) }
62
137
 
63
138
  let(:separator) { "\n" }
64
139
 
@@ -75,6 +150,16 @@ describe Dictionary do
75
150
  subject.starting_with(letter).should be_empty
76
151
  end
77
152
  end
153
+
154
+ describe 'when case-insensitive' do
155
+ let(:case_sensitive) { false }
156
+ it { should_not be_case_sensitive }
157
+ end
158
+
159
+ describe 'when case-sensitive' do
160
+ let(:case_sensitive) { true }
161
+ it { should be_case_sensitive }
162
+ end
78
163
  end
79
164
 
80
165
  describe 'with compressed file' do
@@ -1,7 +1,21 @@
1
1
  require 'spec_helper'
2
2
 
3
3
  describe Dictionary::WordPath do
4
- subject { Dictionary::WordPath.new }
4
+ subject { Dictionary::WordPath.new(case_sensitive) }
5
+
6
+ let(:case_sensitive) { false }
7
+
8
+ describe '#case_sensitive?' do
9
+ describe 'when case-insensitive' do
10
+ it { should_not be_case_sensitive }
11
+ end
12
+
13
+ describe 'when case-sensitive' do
14
+ let(:case_sensitive) { true }
15
+
16
+ it { should be_case_sensitive }
17
+ end
18
+ end
5
19
 
6
20
  describe '#leaf=' do
7
21
  it 'should set to false' do
@@ -16,44 +30,97 @@ describe Dictionary::WordPath do
16
30
  end
17
31
 
18
32
  describe '#find' do
19
- before do
20
- subject << 'potato'
21
- end
33
+ describe 'when case-insensitive' do
34
+ before do
35
+ subject << 'potato'
36
+ end
22
37
 
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' }
38
+ it 'finds existing word paths of matching case-sensitivity' do
39
+ word_path = subject.find('potat')
40
+ word_path.should be_a Dictionary::WordPath
41
+ word_path.should eq Dictionary::WordPath.new(case_sensitive).tap { |wp| wp << 'o' }
42
+ end
43
+
44
+ it 'finds existing word paths of unmatching case-sensitivity' do
45
+ word_path = subject.find('poTAt')
46
+ word_path.should be_a Dictionary::WordPath
47
+ word_path.should eq Dictionary::WordPath.new(case_sensitive).tap { |wp| wp << 'o' }
48
+ end
49
+
50
+ it 'does not find nonexistent word paths' do
51
+ subject.find('potable').should be_nil
52
+ end
27
53
  end
28
54
 
29
- it 'does not find nonexistent word paths' do
30
- subject.find('potable').should be_nil
55
+ describe 'when case-sensitive' do
56
+ before do
57
+ subject << 'poTAto'
58
+ end
59
+
60
+ let(:case_sensitive) { true }
61
+
62
+ it 'finds existing word paths of matching case-sensitivity' do
63
+ word_path = subject.find('poTAt')
64
+ word_path.should be_a Dictionary::WordPath
65
+ word_path.should eq Dictionary::WordPath.new(case_sensitive).tap { |wp| wp << 'o' }
66
+ end
67
+
68
+ it 'does not find existing word paths of unmatching case-sensitivity' do
69
+ subject.find('potat').should be_nil
70
+ end
71
+
72
+ it 'does not find nonexistent word paths' do
73
+ subject.find('potat').should be_nil
74
+ end
31
75
  end
32
76
  end
33
77
 
34
78
  describe '#<<' do
35
79
  before do
36
- subject.find('pot').should be_nil
80
+ subject << 'potato'
37
81
  end
38
82
 
39
- it 'appends word to dictionary' do
40
- subject << 'potato'
41
- subject.find('pot').should_not be_nil
83
+ describe 'when case-insensitive' do
84
+ it 'appends case-insensitive word to word path' do
85
+ subject.find('potato').should be_a Dictionary::WordPath
86
+ subject.find('poTAto').should eq subject.find('potato')
87
+ end
88
+ end
89
+
90
+ describe 'when case-sensitive' do
91
+ let(:case_sensitive) { true }
92
+
93
+ it 'appends case-sensitive word to word path' do
94
+ subject.find('potato').should be_a Dictionary::WordPath
95
+ subject.find('poTAto').should be_nil
96
+ end
42
97
  end
43
98
  end
44
99
 
45
100
  describe '#suffixes' do
46
101
  before do
47
102
  subject << 'pot'
48
- subject << 'potato'
49
- subject << 'potable'
103
+ subject << 'poTAto'
104
+ subject << 'potABle'
105
+ subject << 'POTTY'
50
106
  subject << 'pert'
51
107
  subject << 'jar'
52
108
  end
53
109
 
54
- it 'finds all words with the given suffix' do
55
- word = subject.find('pot')
56
- word.suffixes.should eq %w(ato able)
110
+ describe 'when case-insensitive' do
111
+ it 'finds all words with the given suffix' do
112
+ word = subject.find('pot')
113
+ word.suffixes.should eq %w(ato able ty)
114
+ end
115
+ end
116
+
117
+ describe 'when case-sensitive' do
118
+ let(:case_sensitive) { true }
119
+
120
+ it 'finds all words with the given suffix' do
121
+ word = subject.find('pot')
122
+ word.suffixes.should eq %w(ABle)
123
+ end
57
124
  end
58
125
  end
59
126
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ruby-dictionary
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.1
4
+ version: 1.1.0
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,11 +9,11 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2013-03-24 00:00:00.000000000 Z
12
+ date: 2013-07-27 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: rake
16
- requirement: &2157608560 !ruby/object:Gem::Requirement
16
+ requirement: !ruby/object:Gem::Requirement
17
17
  none: false
18
18
  requirements:
19
19
  - - ! '>='
@@ -21,10 +21,15 @@ dependencies:
21
21
  version: '0'
22
22
  type: :development
23
23
  prerelease: false
24
- version_requirements: *2157608560
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ! '>='
28
+ - !ruby/object:Gem::Version
29
+ version: '0'
25
30
  - !ruby/object:Gem::Dependency
26
31
  name: rspec
27
- requirement: &2157608140 !ruby/object:Gem::Requirement
32
+ requirement: !ruby/object:Gem::Requirement
28
33
  none: false
29
34
  requirements:
30
35
  - - ! '>='
@@ -32,7 +37,12 @@ dependencies:
32
37
  version: '0'
33
38
  type: :development
34
39
  prerelease: false
35
- version_requirements: *2157608140
40
+ version_requirements: !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ! '>='
44
+ - !ruby/object:Gem::Version
45
+ version: '0'
36
46
  description: ! "Dictionary class for ruby that allows for checking\n existence
37
47
  and finding words starting with a given\n prefix."
38
48
  email:
@@ -73,15 +83,21 @@ required_ruby_version: !ruby/object:Gem::Requirement
73
83
  - - ! '>='
74
84
  - !ruby/object:Gem::Version
75
85
  version: '0'
86
+ segments:
87
+ - 0
88
+ hash: -3725738245159260742
76
89
  required_rubygems_version: !ruby/object:Gem::Requirement
77
90
  none: false
78
91
  requirements:
79
92
  - - ! '>='
80
93
  - !ruby/object:Gem::Version
81
94
  version: '0'
95
+ segments:
96
+ - 0
97
+ hash: -3725738245159260742
82
98
  requirements: []
83
99
  rubyforge_project:
84
- rubygems_version: 1.8.10
100
+ rubygems_version: 1.8.25
85
101
  signing_key:
86
102
  specification_version: 3
87
103
  summary: Simple dictionary class for checking existence of words