Portfolio: Phone Number Word Finder
View raw: phone-numbers.rb
#!/usr/bin/ruby -w
# -*- coding: utf-8 -*-
#
# Copyright by Scott Severance
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
# This is a little command-line tool to find words corresponding to a given
# phone number. It is meant to run interactively from the command line. The
# Ubuntu/Debian package 'scowl' should be installed for the best results.
################################################################################
# INITIAL SETUP:
# At least one of $dictionary and $dictDir must be set properly.
# Set the following to the path of a myspell/hunspell dictionary on your system.
# This will also work with a plain text file containing one word per line.
$dictionary = '/usr/share/hunspell/en_US.dic'
# Set the following to the scowl directory.
$dictDir = '/usr/share/dict/scowl'
# Set the following to the encoding of the dictionary file. It is specified in
# the .dic file's corresponding .aff file.
$encoding = 'iso-8859-1'
################################################################################
begin
require 'readline'
$readline = true
rescue LoadError
puts "The readline library is not available on this system. Command editing
will be limited.".gsub(/^[ ]+/, '')
$readline = false
end
class NoNumberError < Exception
end
class PhoneWords
@@wordListCounter = 0
def initialize(num=nil)
@number = num
makeWordList unless defined? @@WordList
end
def listWords
@@WordList
end
def makeWordList
startTime = Time.now.to_f
@@wordListCounter += 1
puts "Building the word list. This may take a while."
# Choose the files to examine for words
#fileBlacklist = [ /(british|canadian|proper-names|roman-numerals|hacker|variant)/, /^[.]+$/ ]
fileList = []
begin
fileList = Dir.entries($dictDir).delete_if do |x|
delete = false
if x =~ /(british|canadian|proper-names|roman-numerals|hacker|variant)/ || File.directory?(x)
delete = true
end
delete
end
rescue Errno::ENOENT
$stderr.puts "The directory '#{$dictDir}' doesn't exist. Skipping..."
end
fileList.collect! { |x| "#{$dictDir}/#{x}" }.push($dictionary)
# Make the word list
wordList = []
fileList.each do |filename|
begin
File.open(filename) do |file|
file.each do |line|
line = line.encode('utf-8', $encoding).upcase.chomp
line.gsub!(/^([^\/\t ]*).*/,'\1').gsub!(/[^A-Z]+/,'')
wordList.push(line) if line.length > 0
end
end
rescue Errno::ENOENT
$stderr.puts "The file '#{filename}' doesn't exist. Skipping..."
end
end
@@WordList = wordList.sort.uniq
endTime = Time.now.to_f
printf "Finished building the word list. It took %.3f seconds to complete.\nThe list contains %d words.\n\n", (endTime - startTime), @@WordList.length
end
private
def trimWordList(len,wordList)
wordList.select { |word| word.length == len }
end
def splitNumber(numberStr)
x = numberStr.gsub(/[^\d-]*/,'')
x.gsub!(/-/,'1')
x.split(/[01]+/)
end
def numToWord(numberStr,wordList)
correlation = {
'A' => 2, 'B' => 2, 'C' => 2,
'D' => 3, 'E' => 3, 'F' => 3,
'G' => 4, 'H' => 4, 'I' => 4,
'J' => 5, 'K' => 5, 'L' => 5,
'M' => 6, 'N' => 6, 'O' => 6,
'P' => 7, 'R' => 7, 'S' => 7, #'Q' => 7,
'T' => 8, 'U' => 8, 'V' => 8,
'W' => 9, 'X' => 9, 'Y' => 9, #'Z' => 9
}
digits = numberStr.split(//)
digits.map! { |n| n.to_i }
words = Array.new
wordList.each do |word|
i = 0
word.split(//).each do |letter|
break unless correlation[letter] == digits[i]
if i == word.length - 1
words.push(word)
break
end
i += 1
end
end
words
end
public
attr_reader :number
def number=(n)
@number = n.to_s
end
def generate(print_results = false)
raise NoNumberError, "No number specified" unless @number
result = []
phoneNumber = splitNumber(@number)
phoneNumber.each do |ph|
next if ph.length <= 1
wordList = trimWordList(ph.length,@@WordList)
options = numToWord(ph,wordList)
if print_results
case options.length
when 0 then printf "There are no options for the number %s.\n", ph
when 1 then printf "The only option for the number %s is %s.\n", ph, options[0]
else printf "The options for the number %s are: %s.\n", ph, options.join(', ')
end
end
result.push({:number => ph, :options => options})
end
result
end
end
def parseCommand(input)
case input
when /^grep /i then runGrep input.gsub(/grep (.*)/i,'\1')
when /^(exit|q(uit)?|bye)$/i then runExit
when nil then runExit
when /\d/ then runNumber input
when /^l(ist)?/i
list = PhoneWords.new.listWords
puts list.join(', ')
puts "Words in list: #{list.length}"
when /^h(elp)?|\?/i then runHelp
else runError input
end
end
def runGrep(pattern)
pat = pattern.gsub(/^\/?(.*)\//,'\1') # Extract the patern from any slashes
begin
re = Regexp.new(pat,"i")
rescue RegexpError
puts "ERROR: Invalid regular expression"
return false
end
p re
puts PhoneWords.new.listWords.grep(re).join(', ')
end
def runExit
puts ''
exit 0
end
def runNumber(num)
PhoneWords.new(num).generate(true)
end
def runHelp
puts <<-EOT.gsub(/^ {4}/, '')
Type any phone number to list the possibilities. The number is split on
"0", "1", and "-". Any other non-digit characters are ignored.
Other Commands:
grep regex: Lists the words matching `regex`, a regular expression as
defined by Ruby. The regex automatically includes the /i
(case-insensitive) switch.
list: Lists all the words in the word list.
exit, quit, q, or bye: Exits.
EOT
end
def runError(input=nil)
puts "Unrecognized input: " + input
end
def main()
while true
begin
if $readline
parseCommand Readline.readline('Command ("h" for help): ', true)
else
print "\nCommand (\"h\" for help): "
parseCommand gets.chomp
end
rescue Interrupt
runExit
end
end
end
main if $0 == __FILE__