Stuck on algorithm. words to nums - ruby

I'm trying to figure out a homework solution in Ruby. I want the answer obviously but you feel it defeats the purpose if I post it here and someone tells me 'this is how you do it'
I am trying to translate fixnum into English word pronunciations, up until the trillionth sizes. So it handles everything from 0 to 999_999_999_999_999
At first I thought of stacking the string as a FILO queue and simply .pop my way through it with a position 'count' to indicate what 'hundred' or 'thousand ' I am currently to spell out. The words, and denomination are set to class instance global hash values to call and verify as a key.
I'm not sure if it's a stupid question but I'm looking for a way to help me think to understand this small problem.
here's my spaghetti code. an improvement to the orignal of 95+ lines.
class Fixnum
##one_to_teen = {0 => "", 1 => 'one', 2 => "two", 3 => 'three', 4 => "four", 5 => 'five', 6 => 'six', 7=> 'seven', 8 => "eight", 9 => 'nine', 10 => 'ten', 11 => "eleven", 12 => 'twelve', 13 => 'thirteen', 14 => "fourteen", 15 => "fifteen", 16 => 'sixteen', 17 => "seventeen", 18 => "eighteen", 19 => "nineteen"}
##tens = {20 => "twenty", 30 => 'thirty', 40 => 'forty', 50 => 'fifty', 60 => 'sixty', 70 => 'seventy', 80 => 'eighty', 90 => 'ninety'}
##count_flag = {3 => "hundred", 4 => "thousand", 9 => "million", 12 => "billion", 15 => "trillion"}
def in_words
return "zero" if self == 0
return ##one_to_teen[self] if self < 20
#stack up
num_stack = self.to_s.split('')
count = 0
temp_str = ""
in_words_str = ""
final_str = ""
#loop until empty
#i was trying to see if resetting the temp_str after being handle in the if statements helped get rid of the stack being used. not originally part of the logic.
while !num_stack.empty?
#puts num_stack.inspect
temp_str << (num_stack.pop unless num_stack.empty?)
count+=1
if count%4 == 0
in_words_str = "#{##one_to_teen["#{temp_str[temp_str.length-1]}".to_i]} #{##count_flag[count]} "
temp_str = ""
elsif count%3 == 0
if temp_str[temp_str.length-1] != "0"
in_words_str = "#{##one_to_teen["#{temp_str[temp_str.length-1]}".to_i]} #{##count_flag[count]} "
#puts temp_str
end
elsif count%2 == 0
in_words_str = "#{##tens[("#{temp_str[1]}0").to_i]} #{##one_to_teen[check_teens(temp_str).to_i]}"
temp_str = ""
end
final_str = in_words_str + final_str
end
#somewhere in my logic i needed to do this, i was getting double spaces due to concat somewhere. bandaided it for now...
return final_str.strip.gsub(" "," ")
end
def check_teens(temp_str)
#swapping here to get correct "20" or higher wording.
if temp_str.reverse.to_i < 20
#puts temp_str.reverse
return temp_str.reverse
else
return temp_str[0]
end
end
end

If you look at how you say 346,422,378 it's three hundred and forty six million four hundred and twenty two thousand three hundred and seventy eight.
You already have the right idea of mapping the groups of three to ones, tens and hundreds. Those then just get repeated.
So the algorithm might go something like:
Use a hash to map word values to each digits in the ones, tens, and hundreds.
Create another hash that contains "trillion", "billion", etc. position map
Get the number and remove any commas, or other formatting.
Is it zero? Easy, done.
Def a sub_string method which takes a group of three and divides that
into three, using your ones, tens hundred mappings. It could return
and array of the word string for each group.
Def a super_string method that then steps through the array you created
and inserts the appropriate word ('trillion', 'million', etc.) between
the array elements.
return your array using something like arr.join(' ') to get one string.
Here's what I would keep:
edited to comply with original spec
class Fixnum
ONES = {0 => "", 1 => 'one', 2 => "two", 3 => 'three', 4 => "four", 5 => 'five', 6 => 'six', 7=> 'seven', 8 => "eight", 9 => 'nine', 10 => 'ten', 11 => "eleven", 12 => 'twelve', 13 => 'thirteen', 14 => "fourteen", 15 => "fifteen", 16 => 'sixteen', 17 => "seventeen", 18 => "eighteen", 19 => "nineteen"}
TENS = {20 => "twenty", 30 => 'thirty', 40 => 'forty', 50 => 'fifty', 60 => 'sixty', 70 => 'seventy', 80 => 'eighty', 90 => 'ninety'}
COUNTS = {3 => "hundred", 4 => "thousand", 9 => "million", 12 => "billion", 15 => "trillion"}
def self.in_words
#fill in code here that uses the other two methods
end
def num_to_word_array(num)
#chop up your number into the substrings and translate
end
def num_words_join(arr)
#join your array using appropriate words, "billion", "million", etc
#return the final string
end
end
Try to divide it into steps that make sense as methods that can be chained. Post any changes you make and I'll be happy to critique.

Related

Use argument ruby as a hash key

I want to create a code to name organic chemestry compounds. How can I use the arguments (num, bond) as keys for the hash? Ignore what I did to global variables, it's just to have a general idea of what I intent to do.
class Molecule
def molecule_name(num, bond)
#num = { 1 => 'met', 2 => 'et', 3=> 'prop', 4 => 'but'}
#bond = {1 => 'ano', 2 => 'eno', 3 => 'ino'}
end
a = Molecule.new; a = a.molecule_name(2,1)
print a
end
The question is a little unclear, but I think this is roughly what you're trying to achieve:
class Molecule
def initialize(num, bond)
#num = num
#bond = bond
end
NAMES = {1 => 'met', 2 => 'et', 3 => 'prop', 4 => 'but'}
BONDS = {1 => 'ano', 2 => 'eno', 3 => 'ino'}
def molecule_name
[ NAMES[#num], BONDS[#bond] ]
end
end
a = Molecule.new(2, 1)
a.molecule_name # => ["et", "ano"]
I tried to modify as little as possible and still get a working example :
class Molecule
attr_reader :num, :bond
def to_s
"#{#num}, #{#bond}"
end
def molecule_name(num, bond)
#num = { 1 => 'met', 2 => 'et', 3=> 'prop', 4 => 'but'}[num]
#bond = {1 => 'ano', 2 => 'eno', 3 => 'ino'}[bond]
end
end
a = Molecule.new
a.molecule_name(2,1)
puts a
#=> et, ano
puts a.num
#=>et
puts a.bond
#=>ano
This example would be a bit more Ruby-ish :
class Molecule
attr_reader :num, :bond
##nums = { 1 => 'met', 2 => 'et', 3=> 'prop', 4 => 'but'}
##bonds = {1 => 'ano', 2 => 'eno', 3 => 'ino'}
def initialize(num_id, bond_id)
#num = ##nums[num_id]
#bond = ##bonds[bond_id]
end
def name
"#{num}, #{bond}"
end
end
a = Molecule.new(2,1)
puts a.name

What's wrong with my 'Bottles of beer on the wall' loop in Ruby

I'm a beginner and I've been stuck on the below for a while; I can't figure out where I'm going wrong.
I am trying to write a program that prints out the 'Bottles of Beer' song, taking in a number and translating it to the equivalent English word, in each iteration of the song.
When I try to run the whole program I get the error message:
in `english_words': undefined method `[]' for nil:NilClass (NoMethodError)
from 11_classes.rb:83:in `block in print_song'
from 11_classes.rb:78:in `downto'
from 11_classes.rb:78:in `print_song'
from 11_classes.rb:116:in `<main>'
But when I test it in irb, the song prints out fine.
Please could someone help explain why this doesn't work when I try to create a new object? I know it's pretty messy and probably quite a long-winded way of doing it but thought trying to do it my own way with what I've learnt so far would be a better way to learn for now.
Thank you!
class BeerSong
attr_accessor :bottles
def initialize(bottles)
bottles = 0 if bottles < 0
bottles = 99 if bottles > 99
#bottles = bottles
end
#single_nums = {
19 => "Nineteen",
18 => "Eighteen",
17 => "Seventeen",
16 => "Sixteen",
15 => "Fifteen",
14 => "Fourteen",
13 => "Thirteen",
12 => "Twelve",
11 => "Eleven",
10 => "Ten",
9 => "nine",
8 => "eight",
7 => "seven",
6 => "six",
5 => "five",
4 => "four",
3 => "three",
2 => "two",
1 => "one",
0 => "Zero"
}
#big_nums = {
9 => "Ninety",
8 => "Eighty",
7 => "Seventy",
6 => "Sixty",
5 => "Fifty",
4 => "Fourty",
3 => "Thirty",
2 => "Twenty"
}
def print_song
#bottles.downto 1 do |n|
if #bottles.zero?
String.new
else
puts """
#{english_words(n)} #{bottle(n)} of beer on the wall,
#{english_words(n)} #{bottle(n)} of beer,
Take one down, pass it around,
#{english_words(n-1)} #{bottle(n+1)} of beer on the wall.
"""
end
end
end
def english_words(bottles)
if bottles <= 19
#single_nums[bottles].capitalize
elsif bottles % 10 == 0
split_number = bottles.to_s.split('').map(&:to_i)
#big_nums[split_number[0]]
else
split_number = bottles.to_s.split('').map(&:to_i)
"#{#big_nums[split_number[0]]}-#{#single_nums[split_number[1]]}"
end
end
def bottle(n)
if n == 1
'bottle'
else
'bottles'
end
end
end
Instance variables #single_nums and #big_nums are defined in terms of an instance and should be set up in initialize.
Move #single_nums = {... and #big_nums = {... into initialize and it should work.
Or you could make them constants: SINGLE_NUMS = {..., BIG_NUMS = {... and leave them where they are.
You are referring to #single_nums and #big_nums from instance methods. But you declared those in the class context.
Move them to the initialize or make them methods like this:
def big_nums
#big_nums ||= {
...your values here...
}
end
This uses memoization so you do not create the hash over and over again.

I want to know what is wrong with my method to_s? Why am I still getting undefined method length if that method is created right above?

This is my code and my error message from my terminal. My project is trying to print out the months of a year like the Cal in the terminal.
class Month
attr_reader :month, :year
def initialize( month, year)
#month = month
#year = year
end
def month_names
names_of_months = {1 => 'January', 2 => 'February', 3 => 'March', 4 => 'April', 5 => 'May', 6 => 'June', 7 => 'July', 8 => 'August', 9 => 'September', 10 => 'October', 11 => 'November', 12 => 'December'}
return names_of_months[#month]
end
def length
days_of_months = {1 => '31', 2 => '28', 3 => '31', 4 => '30', 5 => '31', 6 => '30', 7 => '31', 8 => '31', 9 => '30', 10 => '31', 11 => '30', 12 => '31'}
return days_of_months[#month]
end
def to_s
output = "#{month_names} #{year} #{length}"
(1.length).each do |day|
output << day.to_s
end
output
end
end
and error message:
Error:
TestMonth#test_to_s_on_march_2015:
NoMethodError: undefined method `length' for 1:Fixnum
/Users/brandonespinoza/Desktop/code/RUBY/cal-app/lib/month.rb:22:in `to_s'
test/test_month.rb:56:in `test_to_s_on_march_2015'
You're calling .length on 1, which looks for the method .length on the Fixnum class. To use your length method, try replacing 1.length with just length.

Conditional value extraction from ruby hash

The users of my app have their points. I want to assign them different ranks based on their points. This is my rank mapping hash:
RANKS = { (1..20) => 'Private'
(21..40) => 'Corporal'
(41..60) => 'Sergeant'
(61..80) => 'Lieutenant'
(81..100) => 'Captain'
(101..150) => 'Major'
(151..200) => 'Colonel'
201 => 'General'
}
I need to check if the users' points are in a range key of the hash, and extract the necessary value. Is there any elegant solution for this? I could use 'case' operator, but that wouldn't be as elegant as I want.
You can just iterate all key/value pairs and check.
RANKS = { (1..20) => 'Private',
(21..40) => 'Corporal',
(41..60) => 'Sergeant',
(61..80) => 'Lieutenant',
(81..100) => 'Captain',
(101..150) => 'Major',
(151..200) => 'Colonel',
(201..+1.0/0.0) => 'General', # from 201 to infinity
}
def get_rank score
RANKS.each do |k, v|
return v if k.include?(score)
end
nil
end
get_rank 1 # => "Private"
get_rank 50 # => "Sergeant"
get_rank 500 # => "General"
get_rank -1 # => nil
Update:
I don't know why you think case isn't elegant. I think it's pretty elegant.
def get_rank score
case score
when (1..10) then 'Private'
when (21..40) then 'Corporal'
when (41..1.0/0.0) then 'Sergeant or higher'
else nil
end
end
get_rank 1 # => "Private"
get_rank 50 # => "Sergeant or higher"
get_rank 500 # => "Sergeant or higher"
get_rank -1 # => nil

How to use hash multiple keys to generate a combined string of values ruby

So I have this hash below:
a_hash = {
"1" => "one",
"2" => "two",
"3" => "three",
"4" => "four",
"5" => "five",
"6" => "six",
"7" => "seven",
"8" => "eight",
"9" => "nine",
"10" => "ten",
"11" => "eleven",
"12" => "twelve",
"13" => "thirteen",
"14" => "fourteen",
"15" => "fifteen",
"16" => "sixteen",
"17" => "seventeen",
"18" => "eighteen",
"19" => "nineteen",
"20" => "twenty",
"30" => "thirty",
"40" => "forty",
"50" => "fifty",
"60" => "sixty",
"70" => "seventy",
"80" => "eighty",
"90" => "ninety",
"00" => "hundred", #not sure this is right
"000" => "thousand" #not sure this is right
}
Lets say my string input is "99100".
Lets say I want my string output to be "ninty nine thousand one hundred".
How do I go about using my hash above without typing each key/value. I was thinking maybe split my string at each char into an array....then for each number in that array return my value? Any other stratgies I should consider? This is what I have so far:
puts "test the hash! Type a number hit enter"
test_variable = gets.to_s.chomp
puts a_hash[test_variable]
Post some code so I can try out. Thanks!
Short answer: don't do this yourself, find a library that does it for you.
However, it can be a cool exercise and it is actually an interesting problem. So, ignoring best practices of not reinventing the wheel... You'll probably have to treat hundreds and thousands differently, because you can say "two hundred", but "two seventy" doesn't really make much sense.
Here's my poorly-tested, unoptimised attempt. It is a proof-of-concept and I'm pretty sure I have overlooked many cases. If you want more help, try reading other people's source code.
First we define two hashes, one for numbers, one for magnitudes (which are distinct because they can be prefixed with numbers in order to multiply them).
class Integer
NUMBERS = {
1 => "one",
2 => "two",
3 => "three",
4 => "four",
5 => "five",
6 => "six",
7 => "seven",
8 => "eight",
9 => "nine",
10 => "ten",
11 => "eleven",
12 => "twelve",
13 => "thirteen",
14 => "fourteen",
15 => "fifteen",
16 => "sixteen",
17 => "seventeen",
18 => "eighteen",
19 => "nineteen",
20 => "twenty",
30 => "thirty",
40 => "forty",
50 => "fifty",
60 => "sixty",
70 => "seventy",
80 => "eighty",
90 => "ninety"
}
MAGNITUDES = {
100 => "hundred",
1000 => "thousand",
1000_000 => "million"
}
end
Next, define the conversion method.
class Integer
def to_text
return nil if self == 0
if NUMBERS.keys.include? self
NUMBERS[self]
elsif self < MAGNITUDES.keys.first
base = maximum_fitting(NUMBERS, self)
[NUMBERS[base], (self - base).to_text].compact * "-"
else
base = maximum_fitting(MAGNITUDES, self)
quo, mod = self.divmod(base)
[quo.to_text, MAGNITUDES[base], mod.to_text].compact * " "
end
end
private
def maximum_fitting(list, number)
list.keys.select { |n| n <= number }.last
end
end
To use it:
puts 2351.to_text
#=> "two thousand three hundred fifty-one"
puts 14330132.to_text
#=> "fourteen million three hundred thirty thousand one hundred thirty-two"
puts 1000000.to_text
#=> "one million"
If you just want a solution and not an algorithm to use your hash then Ruby Linguistics could be of service
require 'linguistics'
include Linguistics::EN
numwords(99100)
=> "ninety-nine thousand, one hundred"

Resources