How do I convert a word to phonetic alphabet in Ruby? - ruby

I am trying write a code to convert a word into phonetic alphabet. The desired return value should be something like this:
# >> Type your name and I will convert it to Phonetic Alphabets!
Kevin
# >> Kilo Echo Victor India November
I wrote a hash.
puts "Type your name and I will convert it to Phonetic Alphabets!"
name = gets.chomp
nato_keys = {
"A": "Alpha", "B": "Bravo", "C": "Charlie",
"D": "Delta", "E": "Echo", "F": "Foxtrot",
"G": "Golf", "H": "Hotel", "I": "India",
"J": "Juliett","K": "Kilo", "L": "Lima",
"M": "Mike", "N": "November","O": "Oscar",
"P": "Papa", "Q": "Quebec", "R": "Romeo",
"S": "Sierra", "T": "Tango", "U": "Uniform",
"V": "Victor", "W": "Whiskey", "X": "X-ray",
"Y": "Yankee", "Z": "Zulu"
}
def nato()
puts name.nato_keys.upcase().join(" ")
end
I have an issue with my method as it triggers an error.

Since no one mentioned it, here's a solution with values_at (this assumes string keys in the substitutions hash, as they should be).
str = "Kevin"
nato_keys.values_at(*str.upcase.chars).join(' ')

Assuming the number of words to converted is more than 26 it makes sense to prepare the appropriate hash before doing any conversions.
H = nato_keys.transform_keys(&:to_s)
#=> {"A"=>"Alpha", "B"=>"Bravo",..., "Z"=>"Zulu"}
word = gets.chomp
Supose
word = "Kevin"
Then
word.upcase.each_char.map { |c| H[c] }.join(' ')
#=> "Kilo Echo Victor India November"

You would want to do this:
name.chars.map { |x| nato_keys[x.upcase.to_sym] }.join(' ')
This name.chars give the enumeration with each character of the string name, map iterates on each character and transforms it to the instruction in the block.
block { |x| nato_keys[x.upcase.to_sym] }, picks the relevant mapping for the character, but before that, it converts the key to uppercase and symbol(because in your nato_keys, keys are symbols). 'a' will become :A.
After the block manipulation, we join the result with ' ' to give resultant string.

name.upcase.chars.map(&:intern).map(&nato_keys).join(' ')
First we upcase the entire string. We then split the string into each individual char and turn each into a symbol. Then we can map over the chars sending them one at a time to the nato_keys element reference [], returning the phonetic word, and converting the name. Finally, join them with spaces to format the new string.
EDIT: When using Hash element references we can call the Hash itself.

As Cary Swoveland says, the hash nato_keys, which really does not make sense, has to be converted to one that makes sense:
nato_keys.transform_keys!(&:to_s)
Given:
name = "Kevin"
then,
name.gsub(/(\A)?./){"#{" " unless $1}#{nato_keys[$&.upcase]}"}
# => "Kilo Echo Victor India November"

I would start with preparing more robust hash to transform
transform =
nato_keys.
flat_map do |k, v|
v = v + ' '
[[k.to_s, v], [k.to_s.downcase, v]]
end.
to_h.
tap { |h| h.default_proc = ->(h, k) { h[k] = k } }
Now you might transform your words as easy as:
"Kevin".gsub(/./, transform).strip
#⇒ "Kilo Echo Victor India November"

Related

Split a string delimited by a list of substrings

I have data like:
str = "CODEA text for first item CODEB text for next item CODEB2 some"\
"more text CODEC yet more text"
and a list:
arr = ["CODEA", "CODEB", "CODEB2", "CODEC", ... ]
I want to divide this string into a hash. The keys of the hash will be CODEA, CODEB, etc. The values of the hash will be the text that follows, until the next CODE. The output should look like this:
"CODEA" => "text for first item",
"CODEB" => "text for next item",
"CODEB2" => "some more text",
"CODEC" => "yet more text"
We are given a sting and an array.
str = "CODEA text for first item CODEB text for next item " +
"CODEB2 some more text CODEC yet more text"
arr= %w|CODEC CODEB2 CODEA CODEB|
#=> ["CODEC", "CODEB2", "CODEA", "CODEB"]
This is one way to obtain the desired hash.
str.split.
slice_before { |word| arr.include?(word) }.
map { |word, *rest| [word, rest.join(' ')] }.
to_h
#=> {"CODEA" =>"text for first item",
# "CODEB" =>"text for next item",
# "CODEB2"=>"some more text",
# "CODEC" =>"yet more text"}
See Enumerable#slice_before.
The steps are as follows.
a = str.split
#=> ["CODEA", "text", "for", "first", "item", "CODEB",
# "text", "for", "next", "item", "CODEB2", "some",
# "more", "text", "CODEC", "yet", "more", "text"]
b = a.slice_before { |word| arr.include?(word) }
#=> #<Enumerator:
# #<Enumerator::Generator:0x00005cbdec2b5eb0>:each>
We can see the (4) elements (arrays) that will be generated by this enumerator and passed to each_with_object by converting it to an array.
b.to_a
#=> [["CODEA", "text", "for", "first", "item"],
# ["CODEB", "text", "for", "next", "item"],
# ["CODEB2", "some", "more", "text"],
# ["CODEC", "yet", "more", "text"]]
Continuing,
c = b.map { |word, *rest| [word, rest.join(' ')] }
#=> [["CODEA", ["text for first item"]],
# ["CODEB", ["text for next item"]],
# ["CODEB2", ["some more text"]],
# ["CODEC", ["yet more text"]]]
c.to_h
#=> {"CODEA"=>"text for first item",
# "CODEB"=>"text for next item",
# "CODEB2"=>"some more text",
# "CODEC"=>"yet more text"}
The following is perhaps a better way of doing this.
str.split.
slice_before { |word| arr.include?(word) }.
each_with_object({}) { |(word, *rest),h|
h[word] = rest.join(' ') }
When I was a kid this might be done as follows.
last_word = ''
str.split.each_with_object({}) do |word,h|
if arr.include?(word)
h[word]=''
last_word = word
else
h[last_word] << ' ' unless h[last_word].empty?
h[last_word] << word
end
end
last_word must be set to anything outside the block.
Code:
str = 'CODEA text for first item CODEB text for next item ' +
'CODEB2 some more text CODEC yet more text'
puts Hash[str.scan(/(CODE\S*) (.*?(?= CODE|$))/)]
Result:
{"CODEA"=>"text for first item", "CODEB"=>"text for next item", "CODEB2"=>"some more text", "CODEC"=>"yet more text"}
Another option.
string.split.reverse
.slice_when { |word| word.start_with? 'CODE' }
.map{ |(*v, k)| [k, v.reverse.join(' ')] }.to_h
Enumerator#slice_when, in this case returns this array:
[["text", "more", "yet", "CODEC"], ["text", "more", "some", "CODEB2"], ["item", "next", "for", "text", "CODEB"], ["item", "first", "for", "text", "CODEA"]]
Then the array is mapped to build the required hash to get the result (I did not reversed the Hash):
#=> {"CODEC"=>"yet more text", "CODEB2"=>"some more text", "CODEB"=>"text for next item", "CODEA"=>"text for first item"}
Adding parentheses to the pattern in String#split lets you get both the separators and the fields.
str.split(/(#{Regexp.union(*arr)})/).drop(1).each_slice(2).to_h
# =>
# {
# "CODEA"=>" text for first item ",
# "CODEB"=>"2 somemore text ",
# "CODEC"=>" yet more text"
# }

Finding dictionary words within a source text, using Ruby

Using Ruby, I need to output a list of words, found in a dictionary, that can be formed by eliminating letters from a source text.
E.g., if I input the source text "crazed" I want to get not only words like "craze" and "razed", whose letters are in the same order AND whose letters are adjacent to each other within the source text, but ALSO words like "rad" and "red", because those words exist and can be found by eliminating select letters from "crazed" AND the output words retain letter order. BUT, words like "dare" or "race" should not be in the output list, because the letter order of the letters in "dare" or "race" are not the same as those letters found in "crazed". (If "raed" or "crae" were words in the dictionary, they WOULD be part of the output.)
My thought was to go through the source text in a binary manner
(for "crazed", we'd get:
000001 = "d";
000010 = "e";
000011 = "ed";
000100 = "z";
000101 = "zd";
000111 = "zed";
001000 = "a";
001001 = "ad"; etc.)
and compare each result with words in a dictionary, though I don't know how to code that, nor whether that is most efficient. This is where I would greatly benefit from your help.
Also, the length of the source text would be variable; it wouldn't necessarily be six letters long (like "crazed"). Inputs would potentially be much larger (20-30 characters, possibly more).
I've searched here and found questions about anagrams and about words that can be in any letter order, but not specifically what i'm looking for. Is this even possible in Ruby? Thank you.
First let's read the words of a dictionary into an array, after chomping, downcasing and removing duplicates (if, for example, the dictionary contains both "A" and "a", as does the dictionary on my Mac that I've used below).
DICTIONARY = File.readlines("/usr/share/dict/words").map { |w| w.chomp.downcase }.uniq
#=> ["a", "aa", "aal", "aalii",..., "zyzomys", "zyzzogeton"]
DICTIONARY.size
#=> 234371
The following method generates all combinations of one or more characters of a given word, respecting order, and for each, joins the characters to form a string, checks to see if the string is in the dictionary, and if it is, saves the string to an array.
To check if a string matches a word in the dictionary I perform a binary search, using the method Array#bsearch. This makes use of the fact that the dictionary is already sorted in alphabetical order.
def subwords(word)
arr = word.chars
(1..word.size).each.with_object([]) do |n,a|
arr.combination(n).each do |comb|
w = comb.join
a << w if DICTIONARY.bsearch { |dw| w <=> dw }
end
end
end
subwords "crazed"
# => ["c", "r", "a", "z", "e", "d",
# "ca", "ce", "ra", "re", "ae", "ad", "ed",
# "cad", "rad", "red", "zed",
# "raze", "craze", "crazed"]
Yes, that particular dictionary contains all those strings (such as "z") that don't appear to be English words.
Another example.
subwords "importance"
#=> ["i", "m", "p", "o", "r", "t", "a", "n", "c", "e",
# "io", "it", "in", "ie", "mo", "mr", "ma", "me", "po", "pa", "or",
# "on", "oe", "ra", "re", "ta", "te", "an", "ae", "ne", "ce",
# "imp", "ima", "ion", "ira", "ire", "ita", "ian", "ice", "mor", "mot",
# "mon", "moe", "man", "mac", "mae", "pot", "poa", "pon", "poe", "pan",
# "pac", "ort", "ora", "orc", "ore", "one", "ran", "tan", "tae", "ace",
# "iota", "ione", "iran", "mort", "mora", "morn", "more", "mote",
# "moan", "mone", "mane", "mace", "port", "pore", "pote", "pone",
# "pane", "pace", "once", "rane", "race", "tane",
# "impot", "moran", "morne", "porta", "ponce", "rance",
# "import", "impone", "impane", "prance",
# "portance",
# "importance"]
An extensive solution set that comprises words that can be obtained from using letters in any order is below. The catch with using combination to find possible subwords is that the permutations of the combinations are missed. eg: drawing from 'importance', the combination of 'mpa' will arise at some point. since this isn't a dictionary word, it'll be skipped. thereby costing us, the permutation 'map'-- dictionary subword of 'importance'. below is an extensive solution that finds more possible dictionary words. I agree that my method can be optimized for speed.
#steps
#split string at ''
#find combinations for n=2 all the way to n=word.size
#for each combination
#find the permutations of all the arrangements
#then
#join the array
#check to see if word is in dictionary
#and it's not already collected
#if it is, add to collecting array
require 'set'
Dictionary=File.readlines('dictionary.txt').map(&:chomp).to_set
Dictionary.size #39501
def subwords(word)
#split string at ''
arr=word.split('')
#excluding single letter words
#you can change 2 to 1 in line below to select for single letter words too
(2..word.size).each_with_object([]) do |n,a|
#find combinations for n=2 all the way to n=word.size
arr.combination(n).each do |comb|
#for each combination
#find the permutations of all the arrangements
comb.permutation(n).each do |perm|
#join the array
w=perm.join
#check to see if word is in dictionary and it's not already collected
if Dictionary.include?(w) && !a.include?(w)
#if it is, add to collecting array
a<<w
end
end
end
end
end
p subwords('crazed')
#["car", "arc", "rec", "ace", "cad", "are", "era", "ear", "rad", "red", "adz", "zed", "czar", "care", "race", "acre", "card", "dace", "raze", "read", "dare", "dear", "adze", "daze", "craze", "cadre", "cedar", "crazed"]
p subwords('battle')
#["bat", "tab", "alb", "lab", "bet", "tat", "ate", "tea", "eat", "eta", "ale", "lea", "let", "bate", "beat", "beta", "abet", "bale", "able", "belt", "teat", "tale", "teal", "late", "bleat", "table", "latte", "battle", "tablet"]

Ruby Split string at character difference using regex

I'm current working on a problem that involves splitting a string by each group of characters.
For example,
"111223334456777" #=> ['111','22','333','44','5','6','777']
The way I am currently doing it now is using a enumerator and comparing each character with the next one, and splitting the array that way.
res = []
str = "111223334456777"
group = str[0]
(1...str.length).each do |i|
if str[i] != str[i-1]
res << group
group = str[i]
else
group << str[i]
end
end
res << group
res #=> ['111','22','333','44','5','6','777']
I want to see if I can use regex to do this, which will make this process a lot easier. I understand I could just put this block of code in a method, but I'm curious if regex can be used here.
So what I want to do is
str.split(/some regex/)
to produce the same result. I thought about positive lookahead, but I can't figure out how to have regex recognize that the character is different.
Does anyone have an idea if this is possible?
The chunk_while method is what you're looking for here:
str.chars.chunk_while { |b,a| b == a }.map(&:join)
That will break anything where the current character a doesn't match the previous character b. If you want to restrict to just numbers you can do some pre-processing.
There's a lot of very handy methods in Enumerable that are worth exploring, and each new version of Ruby seems to add more of them.
str = "111333224456777"
str.scan /0+|1+|2+|3+|4+|5+|6+|7+|8+|9+/
#=> ["111", "333", "22", "44", "5", "6", "777"]
or
str.gsub(/(\d)\1*/).to_a
#=> ["111", "333", "22", "44", "5", "6", "777"]
The latter uses the (underused) form of String#gsub that takes one argument and no block, returning an enumerator. It merely generates matches and has nothing to do with character replacement.
For fun, here are several other ways to do that.
str.scan(/((\d)\2*)/).map(&:first)
str.split(/(?<=(.))(?!\1)/).each_slice(2).map(&:first)
str.each_char.slice_when(&:!=).map(&:join)
str.each_char.chunk(&:itself).map { |_,a| a.join }
str.each_char.chunk_while(&:==).map(&:join)
str.gsub(/(?<=(.))(?!\1)/, ' ').split
str.gsub(/(.)\1*/).reduce([], &:<<)
str[1..-1].each_char.with_object([txt[0]]) {|c,a| a.last[-1]==c ? (a.last<<c) : a << c}
Another option which utilises the group_by method, which returns a hash with each individual number as a key and an array of grouped numbers as the value.
"111223334456777".split('').group_by { |i| i }.values.map(&:join) => => ["111", "22", "333", "44", "5", "6", "777"]
Although it doesn't implement a regex, someone else may find it useful.

How to simplify hash intersection with array

I have hash data (originally in json) and an array of selected hash keys:
jsondata ='{"1":
{"name": "Tax Exempt"},
"2":
{"name": "Tax on Purchases"},
"3":
{"name": "Tax on Sales"},
"4":
{"name": "Service Tax"}
}'
parseddata = JSON.parse(jsondata);
selectedtax = ["2","3"]
My code maps the keys and returns the value of the hash that exist in the array. Here is the code:
selectedtaxdetails = Array.new
parseddata.map do |key,value|
if selectedtax.include? key
selectedtaxdetails << value
end
end
Output of selectedtaxdetails is:
[{"name": "Tax on Purchases"},{"name": "Tax on Sales"}]
How can I improve my code?
Solution:
You can do (rails):
parseddata.slice(*selectedtax).values
or even simpler (pure ruby):
parseddata.values_at(*selectedtax)
Explanation:
Both slice and values_at methods expect a list of keys. If you just pass an array it will search for values where this array is a key, whcih obviously is not what you want. Instead you can use a splat operator (*). It will take each element of an array and will pass it into a method as a separate argument, which is exactely what we want here.
Update:
To achieve structure: [{"code":"2", "name": "Tax on Purchase"},{"code":"3", "name": "Tax on Sales"}] you can do (rails):
parseddata.slice(*selectedtax).map {|key, value| value.dup.tap {|h| h['code'] = key}}
or with pure ruby:
parseddata.select{|key,_| selectedtax.include? key}.map {|key, value| value.dup.tap {|h| h['code'] = key}}
The following should do the same in one line
parseddaata.values_at(*selectedtax)

How can I pull a string from an array and then check the first letter of it?

puts "Please Enter First Initial..."
initial = gets
first_letter( name, age, initial)
def first_letter( x, y, z)
index = 0
while index < x.length
--->if z == (x[index])
puts "#{x[index]} #{y[index]}"
end
index += 1
end
end
So essentially what I'm trying to do is use the above code to pull a word from an array and then check the first letter of that string for a match. Essentially it asks the user for a letter and then it checks that letter against the first letter of each string in the array. The marked line is supposed to check the letter against the first letter of the string. If it is equal to the letter, the program is to put the name and age of that entry.
Your question is a bit hard to understand, but the following code selects all the strings from the array where the first letter is an a. Maybe this gets you on the right track:
a #=> ["a", "b", "c", "aa", "bb", "cc", "aaa", "bbb", "ccc"]
a.select { |x| x[0] == ?a } #=> ["a", "aa", "aaa"]
# or
a.select { |x| x.start_with? 'a' } #=> ["a", "aa", "aaa"]
a = %w{ axxx bxxx aaaa cccc azz }
# => ["axxx", "bxxx", "aaaa", "cccc", "azz"]
a.grep(/^a/)
# => ["axxx", "aaaa", "azz"]
Consider Enumerable#grep method with a little bit of regex.

Resources