wordy_number 0.1.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.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: fe333cd990b7fcfb3bb2d85652f668abec088e7f
4
+ data.tar.gz: bc8188687754c1c1966bbb8871c2f8e315f6738f
5
+ SHA512:
6
+ metadata.gz: 35da8b5480e10b28421431680feadcca0a30685b7ea9f056b4c6c3b855659a6fbeba025b443e7c6ee4b980b96803cd02a5affdf9187017a5bb3e973bf551ec7d
7
+ data.tar.gz: 2ead7c0f8b42228e875036c8b7402ae5c06b2038b8f5b76e83c41cedce23371ddda56b6f37d295104735885e35c694b95245f38525ceee3332cbed22d3e4eb28
data/.gitignore ADDED
@@ -0,0 +1,3 @@
1
+ **/*.swp
2
+ /tags
3
+ .byebug_history
data/.rspec ADDED
@@ -0,0 +1,3 @@
1
+ --color
2
+ --format documentation
3
+ --require spec_helper
@@ -0,0 +1,49 @@
1
+ # Contributor Code of Conduct
2
+
3
+ As contributors and maintainers of this project, and in the interest of
4
+ fostering an open and welcoming community, we pledge to respect all people who
5
+ contribute through reporting issues, posting feature requests, updating
6
+ documentation, submitting pull requests or patches, and other activities.
7
+
8
+ We are committed to making participation in this project a harassment-free
9
+ experience for everyone, regardless of level of experience, gender, gender
10
+ identity and expression, sexual orientation, disability, personal appearance,
11
+ body size, race, ethnicity, age, religion, or nationality.
12
+
13
+ Examples of unacceptable behavior by participants include:
14
+
15
+ * The use of sexualized language or imagery
16
+ * Personal attacks
17
+ * Trolling or insulting/derogatory comments
18
+ * Public or private harassment
19
+ * Publishing other's private information, such as physical or electronic
20
+ addresses, without explicit permission
21
+ * Other unethical or unprofessional conduct
22
+
23
+ Project maintainers have the right and responsibility to remove, edit, or
24
+ reject comments, commits, code, wiki edits, issues, and other contributions
25
+ that are not aligned to this Code of Conduct, or to ban temporarily or
26
+ permanently any contributor for other behaviors that they deem inappropriate,
27
+ threatening, offensive, or harmful.
28
+
29
+ By adopting this Code of Conduct, project maintainers commit themselves to
30
+ fairly and consistently applying these principles to every aspect of managing
31
+ this project. Project maintainers who do not follow or enforce the Code of
32
+ Conduct may be permanently removed from the project team.
33
+
34
+ This code of conduct applies both within project spaces and in public spaces
35
+ when an individual is representing the project or its community.
36
+
37
+ Instances of abusive, harassing, or otherwise unacceptable behavior may be
38
+ reported by contacting a project maintainer at munish.goyal@universumglobal.com. All
39
+ complaints will be reviewed and investigated and will result in a response that
40
+ is deemed necessary and appropriate to the circumstances. Maintainers are
41
+ obligated to maintain confidentiality with regard to the reporter of an
42
+ incident.
43
+
44
+ This Code of Conduct is adapted from the [Contributor Covenant][homepage],
45
+ version 1.3.0, available at
46
+ [http://contributor-covenant.org/version/1/3/0/][version]
47
+
48
+ [homepage]: http://contributor-covenant.org
49
+ [version]: http://contributor-covenant.org/version/1/3/0/
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in wordy_number.gemspec
4
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2016 Munish Goyal
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,73 @@
1
+ # WordyNumber
2
+
3
+ Along with being a gem, WordyNumber is a command line application that reads from files specified as command-line arguments or STDIN when no files are given. Each line of the file is suppose to contain a single phone number.
4
+
5
+ For every phone number read, the gem outputs all possible word replacements from a dictionary. It tries to replace every digit of the provided phone number with a letter from a dictionary word; however, if no match can be made, a single digit will be left as is at that point. No two consecutive digits will remain unchanged and the program skips over a number (producing on output) if a match cannot be made.
6
+
7
+ All punctuations and whitespaces are ignored in both phone numbers and the dictionary file. Also, the program is not case sensitive.
8
+
9
+ Output is in capital letters with words and digits separated with a single dash (-).
10
+
11
+ For example, if the program is fed the number:
12
+
13
+ 2255.63
14
+
15
+ One possible line of output is:
16
+
17
+ CALL-ME
18
+
19
+ According to the default dictionary.
20
+
21
+ The number encoding on the phone, the program uses is:
22
+
23
+ | Digit | CHARACTERS |
24
+ |:---------|:-----------|
25
+ | 2 | A,B,C |
26
+ | 3 | D,E,F |
27
+ | 4 | G,H,I |
28
+ | 5 | J,K,L |
29
+ | 6 | M,N,O |
30
+ | 7 | P,Q,R,S |
31
+ | 8 | T,U,V |
32
+ | 9 | W,X,Y,Z |
33
+
34
+ WordList Sources:
35
+
36
+ http://www-01.sil.org/linguistics/wordlists/english/
37
+
38
+ ## Installation
39
+
40
+ Add this line to your application's Gemfile:
41
+
42
+ ```ruby
43
+ gem 'wordy_number'
44
+ ```
45
+
46
+ And then execute:
47
+
48
+ $ bundle
49
+
50
+ Or install it yourself as:
51
+
52
+ $ gem install wordy_number
53
+
54
+ ## Usage
55
+
56
+ > sub = WordyNumber::WordyMatch.new
57
+ > sub.display_matches num_list: [2255,225563,8587071016]
58
+
59
+ ## Development
60
+
61
+ After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
62
+
63
+ To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
64
+
65
+ ## Contributing
66
+
67
+ Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/wordy_number. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](http://contributor-covenant.org) code of conduct.
68
+
69
+
70
+ ## License
71
+
72
+ The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
73
+
data/Rakefile ADDED
@@ -0,0 +1,6 @@
1
+ require "bundler/gem_tasks"
2
+ require "rspec/core/rake_task"
3
+
4
+ RSpec::Core::RakeTask.new(:spec)
5
+
6
+ task :default => :spec
data/bin/console ADDED
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "bundler/setup"
4
+ require "wordy_number"
5
+
6
+ # You can add fixtures and/or initialization code here to make experimenting
7
+ # with your gem easier. You can also use a different console, if you like.
8
+
9
+ # (If you use this, don't forget to add pry to your Gemfile!)
10
+ # require "pry"
11
+ # Pry.start
12
+
13
+ require "irb"
14
+ IRB.start
data/bin/setup ADDED
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
@@ -0,0 +1,64 @@
1
+ module WordyNumber
2
+ class DictWord
3
+ attr_accessor :word_form, :numeric_form
4
+
5
+ DIGIT_CHARACTER_MAP = {
6
+ :A => 2,
7
+ :B => 2,
8
+ :C => 2,
9
+ :D => 3,
10
+ :E => 3,
11
+ :F => 3,
12
+ :G => 4,
13
+ :H => 4,
14
+ :I => 4,
15
+ :J => 5,
16
+ :K => 5,
17
+ :L => 5,
18
+ :M => 6,
19
+ :N => 6,
20
+ :O => 6,
21
+ :P => 7,
22
+ :Q => 7,
23
+ :R => 7,
24
+ :S => 7,
25
+ :T => 8,
26
+ :U => 8,
27
+ :V => 8,
28
+ :W => 9,
29
+ :X => 9,
30
+ :Y => 9,
31
+ :Z => 9,
32
+ }
33
+
34
+ def initialize(word)
35
+ @word_form = word
36
+ sanitize_word!
37
+ word_to_numeric_string!
38
+ end
39
+
40
+ def to_s
41
+ "#{word_form}: #{numeric_form}"
42
+ end
43
+
44
+ private
45
+
46
+ def sanitize_word!
47
+ word = word_form
48
+ word.strip!
49
+ word.upcase!
50
+ word.gsub!(/[^A-Z]/, "")
51
+ self.word_form = word
52
+ end
53
+
54
+ def word_to_numeric_string!
55
+ numeric_string = ""
56
+ word_form.split("").each do |c|
57
+ numeric_string << DIGIT_CHARACTER_MAP[c.capitalize.to_sym].to_s
58
+ end
59
+
60
+ self.numeric_form = numeric_string
61
+ end
62
+ end
63
+ end
64
+
@@ -0,0 +1,3 @@
1
+ module WordyNumber
2
+ VERSION = "0.1.0"
3
+ end
@@ -0,0 +1,241 @@
1
+ require_relative 'dict_word'
2
+
3
+ module WordyNumber
4
+ class WordyMatch
5
+
6
+ # Dictioary hash structures looks like following
7
+ # dict_hash = {
8
+ # 1 => [{:word_form=>"a", :numeric_form=>"2"}],
9
+ # ,...,
10
+ # 23 => [{:word_form=>"disestablismentarianism", :numeric_form=>"34737822547636827426476"},
11
+ # {:word_form=>"electroencephalographic", :numeric_form=>"35328763623742564727442"}],
12
+ # }
13
+
14
+ PUBLIC_DIR = File.expand_path("../../public", File.dirname(__FILE__))
15
+ DEFAULT_DICT_FILE_NAME = "wordsEn.txt"
16
+ DEFAULT_DICT_FILE_PATH = File.expand_path(DEFAULT_DICT_FILE_NAME, PUBLIC_DIR)
17
+ DEFAULT_DICT_FACTS = {
18
+ number_of_keys: 25,
19
+ number_of_words: 109582,
20
+ }
21
+ DEFAULT_SEPARATOR = "-"
22
+
23
+ attr_accessor :dict
24
+ attr_accessor :num, :num_patterns, :num_filtered_patterns
25
+
26
+ # dictionary is expected to have one word per line
27
+ def initialize(user_dictionary=DEFAULT_DICT_FILE_PATH)
28
+ @dict = user_dictionary
29
+ scan_dict!(dict)
30
+ end
31
+
32
+ def num=(user_number)
33
+ @num = user_number.to_s
34
+ @num.strip!
35
+ @num.gsub!(/\D/, "")
36
+
37
+ @num
38
+ end
39
+
40
+ def dict_hash
41
+ @dict_hash = {} unless @dict_hash
42
+
43
+ @dict_hash
44
+ end
45
+
46
+ def display_dict_stats
47
+ dh = dict_hash
48
+ dh.keys.each{ |key| puts "#{key} -> #{dh[key].size}" }
49
+ end
50
+
51
+ # sub = WordyMatch.new
52
+ # sub.display_matches num_file: "my_nums.txt"
53
+ # sub.display_matches num_list: [2255, 225563]
54
+ def display_matches(args = {num_file: nil, num_list: nil})
55
+ # get user inputs
56
+ num_file = args[:num_file]
57
+ num_list = args[:num_list]
58
+ num_file_path = File.expand_path(num_file, PUBLIC_DIR) if num_file
59
+ results = {}
60
+
61
+ # form numbers array
62
+ numbers = []
63
+ if num_file_path
64
+ File.open(num_file_path, "r") do |file|
65
+ file.each_line do |line|
66
+ numbers << line
67
+ end
68
+ end
69
+ elsif num_list.class == Array
70
+ numbers = num_list
71
+ else
72
+ # user hasn't provide num_file, so ask him/her explicitly
73
+ puts "Comma-separated list of numbers: "
74
+ numbers = gets.strip.split(",")
75
+ end
76
+
77
+ # get output
78
+ numbers.each do |number|
79
+ puts "---------------------------------------"
80
+ puts "Matches for #{number.to_s}"
81
+ matches = set_num_and_find_matches(number)
82
+ results[number.to_s] = matches
83
+ if matches.size > 0
84
+ matches.each do |match|
85
+ puts match
86
+ end
87
+ else
88
+ puts "No matches"
89
+ end
90
+ end
91
+
92
+ results
93
+ end
94
+
95
+ def set_num_and_find_matches(user_number)
96
+ set_num(user_number)
97
+
98
+ # find matches and filter them
99
+ split_arnd_0_1_n_find_matches
100
+ filtered_list
101
+ end
102
+
103
+ def self.concat_array_of_lists_of_strings(array_of_lists, separator=DEFAULT_SEPARATOR)
104
+ # remove empty elements
105
+ array_of_lists.each do |list|
106
+ list.delete_if{ |elem| elem.length == 0 }
107
+ end
108
+ # remove empty lists
109
+ array_of_lists.select!{ |list| list.size > 0 }
110
+
111
+ # calculate product
112
+ result = if array_of_lists.size > 1
113
+ array_of_lists[0].product(*array_of_lists[1..-1]).map{ |elem| elem.join(separator) }
114
+ elsif array_of_lists.size == 1
115
+ array_of_lists[0]
116
+ else
117
+ # array_of_lists.size == 0
118
+ []
119
+ end
120
+
121
+ result
122
+ end
123
+
124
+ def self.join_numbers_together_in_str(num_str, separator=DEFAULT_SEPARATOR)
125
+ res_str = num_str.gsub(/(\d)#{DEFAULT_SEPARATOR}+(\d)/, '\1\2')
126
+ if res_str != num_str
127
+ return join_numbers_together_in_str(res_str)
128
+ else
129
+ return res_str
130
+ end
131
+ end
132
+
133
+ private
134
+
135
+ # set_num() is kind of alias to num=(), but helps in chaining
136
+ def set_num(user_number)
137
+ self.num = user_number
138
+
139
+ self
140
+ end
141
+
142
+ def find_all_matches(num_str=self.num, original_call=true, pattern_length=num_str.length)
143
+ patterns = []
144
+ if num_str.size == 0 # first deal with the edge cases
145
+ return patterns
146
+ end
147
+ if pattern_length == 1 # first deal with the edge cases
148
+ if dict_hash[pattern_length]
149
+ times = 0
150
+ dict_hash[pattern_length].each do |dict_word|
151
+ if num_str == dict_word
152
+ times += 1
153
+ patterns << dict_word.word_form
154
+ end
155
+ end
156
+ if times == 0
157
+ patterns << num_str
158
+ end
159
+ end
160
+ elsif pattern_length > 0 # general case
161
+ if dict_hash[pattern_length]
162
+ dict_hash[pattern_length].each do |dict_word|
163
+ matched_index = num_str =~ /#{dict_word.numeric_form}/
164
+ if matched_index
165
+ str1 = matched_index > 0 ? num_str[0..(matched_index - 1)] : ""
166
+ str2 = dict_word.word_form # it has word_form, but str1 and str3 still have numeric_form
167
+ str3 = num_str[(matched_index + pattern_length)..-1]
168
+ patterns += self.class.concat_array_of_lists_of_strings([find_all_matches(str1, false), [str2], find_all_matches(str3, false)])
169
+ end
170
+ end
171
+ end
172
+ patterns += find_all_matches(num_str, false, pattern_length-1)
173
+ else
174
+ raise NonReachableCodeError
175
+ end
176
+ patterns.uniq!
177
+
178
+ if original_call
179
+ self.num_patterns = patterns
180
+ end
181
+
182
+ return patterns
183
+ end
184
+
185
+ # filter out patterns with consecutive digits
186
+ def filtered_list(list_of_num_strs=self.num_patterns.clone)
187
+ unless list_of_num_strs
188
+ raise InvalidInputError
189
+ end
190
+ list_of_num_strs.delete_if{ |pattern| pattern.split(DEFAULT_SEPARATOR).join("") =~ /\d{2}/ }
191
+ self.num_filtered_patterns = list_of_num_strs
192
+
193
+ self.num_filtered_patterns
194
+ end
195
+
196
+ # it enhances the performance of long numbers carrying 1s or 0s
197
+ def split_arnd_0_1_n_find_matches(num_str=self.num)
198
+ # split given number_string at 0's and 1's
199
+ splits = num_str.gsub(/[^01]/, "").split("")
200
+ split_num_str = num_str.split(/[01]/)
201
+ # obtain patterns for each sub_number_string
202
+ split_results = split_num_str.map do |sub_num_str|
203
+ find_all_matches(sub_num_str)
204
+ end
205
+ # obtain combined result
206
+ combined_splits_and_split_results = []
207
+ split_results.each_index do |index|
208
+ combined_splits_and_split_results << split_results[index]
209
+ combined_splits_and_split_results << [splits[index]] if index <= splits.length - 1
210
+ end
211
+ combined_splits_and_split_results.select!{ |x| x.length > 0 }
212
+ results = self.class.concat_array_of_lists_of_strings(combined_splits_and_split_results)
213
+ # join numbers togeter in each results element
214
+ results.map!{ |str| self.class.join_numbers_together_in_str(str) }
215
+ # set num_patterns
216
+ self.num_patterns = results
217
+
218
+ results
219
+ end
220
+
221
+ def scan_dict!(dict_file_path)
222
+ dict_file_path = dict_file_path || dict || DEFAULT_DICT_FILE_PATH
223
+
224
+ # reset dict_hash
225
+ dh = dict_hash
226
+ dh.clear
227
+
228
+ # load dictionary
229
+ File.open(dict_file_path, "r") do |file|
230
+ file.each_line do |line|
231
+ dw = DictWord.new(line)
232
+ dh[dw.word_form.length] = [] unless dh[dw.word_form.length] # `dw.word` is sanitized but `line` is not
233
+ dh[dw.word_form.length] << DictWord.new(line)
234
+ end
235
+ end
236
+
237
+ dh
238
+ end
239
+ end
240
+ end
241
+
@@ -0,0 +1,16 @@
1
+ require "wordy_number/version"
2
+ require "wordy_number/dict_word"
3
+ require "wordy_number/wordy_match"
4
+
5
+ module WordyNumber
6
+ # custom Exceptions
7
+ class Error < StandardError;end
8
+
9
+ class StartsWithNonRepeatableRomanUnitError < Error;end
10
+
11
+ class NonReachableCodeError < Error;end
12
+
13
+ class InvalidInputError < Error;end
14
+
15
+ end
16
+
@@ -0,0 +1,3 @@
1
+ 2255
2
+ 225563
3
+ 8587071016