In a coding challenge I recently attempted, I found this as an alternative solution to converting numbers to Roman numerals. I don't really understand how this code works. I just figured out what divmod does but else, I am very confused.
class Integer
def to_roman
roman_arr = {
1000 => "M",
900 => "CM",
500 => "D",
400 => "CD",
100 => "C",
90 => "XC",
50 => "L",
40 => "XL",
10 => "X",
9 => "IX",
5 => "V",
4 => "IV",
1 => "I"
}
num = self
roman_arr.reduce("") do |res, (arab, roman)|
whole_part, num = num.divmod(arab)
res << roman * whole_part
end
end
end
reduce / fold is the functional programming equivalent to the looping constructs found in imperative languages. ruby is capable of both.
foo.reduce("") { |a, i| a + i } is equivalent to
a = ""
foo.each {|i| a = a + i}
a
the num = self line saves the instance (the number which receives the to_roman method) in a local variable so you can use it in the block that you pass to reduce.
I added some explanations to the code, hope it is clear now.
You would never write it like this though, the code you published is fine.
class Integer
def to_roman_explained
roman_arr = {
1000 => "M",
900 => "CM",
500 => "D",
400 => "CD",
100 => "C",
90 => "XC",
50 => "L",
40 => "XL",
10 => "X",
9 => "IX",
5 => "V",
4 => "IV",
1 => "I"
}
remaining = self # the integer on which this method is called
empty_string = "" # startvalue of result
return_value = roman_arr.inject(empty_string) do |result, (arab, roman)|
# inject = reduce, for each element of our hash
# arab and roman are the key and value part of the hash elements, result is result from previous iteration
p [result, arab, roman,remaining.divmod(arab)] # lets's see what happens
# number of times the remaining can be divided with the value of this roman, the remaining becomes the rest
whole_part, remaining = remaining.divmod(arab)
result << roman * whole_part # if whole_part == 0 nothing happens for this roman
end
return return_value
end
end
puts 555.to_roman_explained
# gives
# ["", 1000, "M", [0, 555]]
# ["", 900, "CM", [0, 555]]
# ["", 500, "D", [1, 55]] first time the integer is dividable by the value of the roman
# ["D", 400, "CD", [0, 55]] our result now has the roman D
# ["D", 100, "C", [0, 55]]
# ["D", 90, "XC", [0, 55]]
# ["D", 50, "L", [1, 5]] etc
# ["DL", 40, "XL", [0, 5]]
# ["DL", 10, "X", [0, 5]]
# ["DL", 9, "IX", [0, 5]]
# ["DL", 5, "V", [1, 0]] etc
# ["DLV", 4, "IV", [0, 0]]
# ["DLV", 1, "I", [0, 0]]
# DLV
I found its easier if reversing the number beforehand, like:
def new_roman(arabic_number)
symbols = {0=>["I","V"],1=>["X","L"],2=>["C","D"],3=>["M"]}
reversed_digits = arabic_number.to_s.split(//).reverse
romans =[]
reversed_digits.length.times do |i|
if reversed_digits[i].to_i< 4
romans<<(symbols[i][0]*reversed_digits[i].to_i)
elsif reversed_digits[i].to_i == 4
romans<<(symbols[i][0]+ symbols[i][1])
elsif reversed_digits[i].to_i == 9
romans<<(symbols[i][0] + symbols[i+1][0])
else
romans<<(symbols[i][1] + (symbols[i][0]*((reversed_digits[i].to_i)-5)))
end
end
romans.reverse.join("")
end
Related
Closed. This question needs to be more focused. It is not currently accepting answers.
Want to improve this question? Update the question so it focuses on one problem only by editing this post.
Closed 2 years ago.
Improve this question
I'm trying to write code that simulates writing a text message using a multi-tap telephone keypad in Ruby. This is the telephone keypad:
1 2 3
ABC DEF
4 5 6
GHI JKL MNO
7 8 9
PQRS TUV WXYZ
0
(space)
I tried to define it in Ruby as: (doesn't work)
"0" = [" "] # (adds a space)
"1" = [""] # (adds nothing)
"2" = ["a", "b", "c"]
"3" = ["d", "e", "f"]
"4" = ["g", "h", "i"]
"5" = ["j", "k", "l"]
"6" = ["m", "n", "o"]
"7" = ["p", "q", "r", "s"]
"8" = ["t", "u", "v"]
"9" = ["w", "x", "y", "z"]
I will explain how it works with two examples. First I will send the string goat. To send the g I press 4 once. Next, to send o I press 6 three times (as pressing 6 once would send m and pressing 6 twice would send n). For a press 2 once and for t press 8 once. We therefore would send
466628
g oat
Next, consider cake. By the same procedure we would send
22225533
ca k e
Here there is problem. When decoding this there are several possibilities for 2222. It could be aaaa, bb and so on. To overcome this ambiguity a "pause", represented as a space, is inserted after each string of digits that is followed by a string of the same digit. For cake, therefore, we would write
222 25533
c a k e
I already have a hash with the numbers and its corresponding letters, and I know that I have to sort the numbers by how many times they repeat themselves. But I do not know which method I use for it.
Also, do I have to use the same logic in case I need to encode (number to letter)?
(I had the encoding part first when Cary Swoveland pointed out that you might want decoding. The answer now contains both ways and became quite long, I hope you don't mind)
Your example code doesn't work. You can't just assign to a string literal. However, you could use a hash like this to define your keypad in Ruby:
keypad = {
'0' => [' '],
'1' => [], # <- you can leave this out
'2' => %w[a b c],
'3' => %w[d e f],
'4' => %w[g h i],
'5' => %w[j k l],
'6' => %w[m n o],
'7' => %w[p q r s],
'8' => %w[t u v],
'9' => %w[w x y z],
}
Decoding
To turn 222 25533 into cake, I'd start by splitting consecutive and space-delimited numbers. You could use a regex:
parts = '222 25533'.gsub(/(\d)\1*/).map(&:itself)
#=> ["222", "2", "55", "33"]
This can be converted to an array containing key/number-of-times pairs:
key_strokes = parts.map { |part| [part[0], part.length] }
#=> [["2", 3], ["2", 1], ["5", 2], ["3", 2]]
which can be converted to the letters using the keypad hash:
letters = key_strokes.map { |key, times| key_pad[key][times - 1] }
#=> ["c", "a", "k", "e"]
That - 1 is needed because array indices are zero-based. Finally, turn the letters into a word:
letters.join
#=> "cake"
Encoding
To convert characters to key strokes, I'd create a hash based on the keypad which maps each character to a key/number-of-times pair:
mapping = {}
key_pad.each do |key, values|
values.each.with_index(1) do |char, times|
mapping[char] = [key, times]
end
end
mapping
#=> {
# " "=>["0", 1], "a"=>["2", 1], "b"=>["2", 2], "c"=>["2", 3], "d"=>["3", 1],
# "e"=>["3", 2], "f"=>["3", 3], "g"=>["4", 1], "h"=>["4", 2], "i"=>["4", 3],
# "j"=>["5", 1], "k"=>["5", 2], "l"=>["5", 3], "m"=>["6", 1], "n"=>["6", 2],
# "o"=>["6", 3], "p"=>["7", 1], "q"=>["7", 2], "r"=>["7", 3], "s"=>["7", 4],
# "t"=>["8", 1], "u"=>["8", 2], "v"=>["8", 3], "w"=>["9", 1], "x"=>["9", 2],
# "y"=>["9", 3], "z"=>["9", 4]
# }
In the above hash, "c"=>["2", 3] means that in order to get c you have to press the 2 key 3 times. To render the sequence for a single key in Ruby, we can utilize String#* which repeats a string:
key, times = mapping['c']
key #=> '2'
times #=> 3
key * times
#=> '222'
Getting the key strokes for an entire word (or sentence) is a matter of mapping each character to its respective hash value:
parts = 'cake'.each_char.map { |char| mapping[char] }
#=> [["2", 3], ["2", 1], ["5", 2], ["3", 2]]
To render the actual sequence, we have to first group consecutive runs of the same key:
chunks = parts.chunk_while { |(a, _), (b, _)| a == b }.to_a
#=> [
# [["2", 3], ["2", 1]],
# [["5", 2]],
# [["3", 2]]
# ]
We can now join identical key strokes via space and the chunks without space:
chunks.map { |chunk| chunk.map { |k, t| k * t }.join(' ') }.join
#=> "222 25533"
You are given:
arr = [
["0", [" "]],
["1", [""]],
["2", ["a", "b", "c"]],
["3", ["d", "e", "f"]],
["4", ["g", "h", "i"]],
["5", ["j", "k", "l"]],
["6", ["m", "n", "o"]],
["7", ["p", "q", "r", "s"]],
["8", ["t", "u", "v"]],
["9", ["w", "x", "y", "z"]]
]
I have been puzzled by the inclusion of
["1", [""]],
which seems to serve no purpose. It would have a purpose, however, if instead of
222 25533
to represent the string, "cake", we used
222125533
That is, if two successive characters that are represented by strings of the same digit (such as "222" and "2") they are to be separated by a "1", rather than by a "pause", expressed as a space. If that were done we could encode and decode strings as follows.
Encoding
CHAR_TO_DIGITS = arr.each_with_object({}) do |(num, a),h|
a.each.with_index(1) { |ltr,i| h[ltr] = num * i }
end
#=> {" "=>"0", ""=>"1", "a"=>"2", "b"=>"22", "c"=>"222",
# "d"=>"3", "e"=>"33", "f"=>"333", "g"=>"4", "h"=>"44",
# "i"=>"444", "j"=>"5", "k"=>"55", "l"=>"555", "m"=>"6",
# "n"=>"66", "o"=>"666", "p"=>"7", "q"=>"77", "r"=>"777",
# "s"=>"7777", "t"=>"8", "u"=>"88", "v"=>"888", "w"=>"9",
# "x"=>"99", "y"=>"999", "z"=>"9999"}
def encode(plain_text)
plain_text.each_char.with_object('') do |c,s|
digits = CHAR_TO_DIGITS[c]
s << '1' if !s.empty? && digits[0] == s[-1]
s << digits
end
end
Then
encoded_1 = encode "cake"
#=> "222125533"
encoded_2 = encode "my dog has fleas"
#=> "69990366640442777703335553327777"
Decoding
Decoding is even easier.
DIGITS_TO_CHAR = CHAR_TO_DIGITS.invert
#=> {"0"=>" ", "1"=>"", "2"=>"a", "22"=>"b", "222"=>"c",
# "3"=>"d", "33"=>"e", "333"=>"f", "4"=>"g", "44"=>"h",
# "444"=>"i", "5"=>"j", "55"=>"k", "555"=>"l", "6"=>"m",
# "66"=>"n", "666"=>"o", "7"=>"p", "77"=>"q", "777"=>"r",
# "7777"=>"s", "8"=>"t", "88"=>"u", "888"=>"v", "9"=>"w",
# "99"=>"x", "999"=>"y", "9999"=>"z"}
def decode(encoded_text)
encoded_text.gsub(/(\d)\1*/, DIGITS_TO_CHAR)
end
Then
decode encoded_1
#=> "cake"
decode encoded_2
#=> "my dog has fleas"
This uses the form of String#gsub that employs a hash to make substitutions. See also Hash#invert.
I'd start with converting "222 25533" into an array [[2,3],[2,1],[5,2],[3,2]] where the first number represents a digit and the second is a number of its occurrences.
Having this you can easily find letters from the keypad.
I'm trying to create a program that would take a string from user input and return the 'value' of the word where a=1, b=2, c=3 etc. i.e. "cab" = 6.
Unfortunately I can't figure out how to break down the user input variable and have it added together:
print "Give us a word to calculate: "
word = gets.chomp
alphabet = Hash[
"a" => 1, "b" => 2, "c" => 3,
"d" => 4, "e" => 5, "f" => 6,
"g" => 7, "h" => 8, "i" => 9,
"j" => 10, "k" => 11, "l" => 12,
"m" => 13, "n" => 14, "o" => 15,
"p" => 16, "q" => 17, "r" => 18,
"s" => 19, "t" => 20, "u" => 21,
"v" => 22, "w" => 23, "x" => 24,
"y" => 25, "z" => 26
]
value = word.split("")
puts "Your word, \'#{word}\' has a value of: #{value}"
You can use reduce method to add up the values of each char.
value = word.split("")
sum = value.reduce(0) {|sum, char| alphabet[char] + sum }
puts "Your word, \'#{word}\' has a value of: #{sum}"
#=> Your word, 'cab' has a value of: 6
Here we use reduce (which has an alias method inject) to reduce the array into a single value. We start with initial value of 0, and iterate through each element of the array - in the block, we add the numeric equivalent of given char to the sum so far - and eventually end up with sum of all numeric values.
Answer to question in comments:
My only relevant follow-up question to this, is it possible to define
the hash using ranges? I know that I can define them with ("a".."z")
and (1..26) but I didn't know if there is a way to set those two
ranges equal to one another based on their index values or somesuch
You can make use of Array#zip method that allows to merge two arrays by pairing elements at same index as sub-arrays. Subsequently, we can take advantage of method Array#to_h which converts any array of 2-element arrays into hash.
alphabet = ('a'..'z').zip(1..26).to_h
I'd suggest the following as a good Ruby-way:
base = 'a'.ord-1
"catsup".each_char.map { |c| c.ord - base }.reduce(:+)
#=> 80
Breaking it down:
d = 'a'.ord
#=> 97
base = d-1
#=> 96
e = "catsup".each_char.map { |c| c.ord - base }
#=> [3, 1, 20, 19, 21, 16]
e.reduce(:+)
#=> 80
Let's look more carefully at the calculation of e:
enum0 = "catsup".each_char
#=> #<Enumerator: "catsup":each_char>
Note:
enum0.map { |c| c.ord - base }
#=> [3, 1, 20, 19, 21, 16]
To see the elements of the enumerator enum0, which will be passed to map, convert it to an array:
enum0.to_a
#=> ["c", "a", "t", "s", "u", "p"]
Now lets write:
enum1 = enum0.map
#=> #<Enumerator: #<Enumerator: "catsup":each_char>:map>
Study the return value. You can think of enum1 as a "compound" enumerator.
enum1.to_a
#=> ["c", "a", "t", "s", "u", "p"]
enum1.each { |c| c.ord - base }
#=> [3, 1, 20, 19, 21, 16]
We can now use Enumerator#next to extract each element of enum, set the block variable c to that value and perform the block calculation:
c = enum1.next #=> "c"
c.ord - base #=> 99-96 = 3
c = enum1.next #=> "a"
c.ord - base #=> 1
c = enum1.next #=> "t"
c.ord - base #=> 20
c = enum1.next #=> "s"
c.ord - base #=> 19
c = enum1.next #=> "u"
c.ord - base #=> 21
c = enum1.next #=> "p"
c.ord - base #=> 16
c = enum1.next #=> StopIteration: iteration reached an end
Lets say you have a string
initial_message = "My dear cousin bill!"
I put this string of N characters in an array of hashes (where each letter is the key and the value is A = 0 , B = 1, C = 2.. etc).
hsh_letter_values = Hash[('a'..'z').zip (0..25).to_a] #Map letters to numbers in a hash
clean_message = initial_message.tr('^a-zA-Z0-9','').downcase #remove non-letters
char_map = clean_message.each_char.map { |i| { i => hsh_letter_values[i] } } #map each letter of message to corresponding number
Then I split the char_map into slices of 16.
char_split_map = char_map.each_slice(16).to_a
I want to split each 16 character slice into slices of 4, while keeping the hashes in the same order.
The outcome should look like:
[[[{"m"=>12}, {"y"=>24}, {"d"=>3}, {"e"=>4}],[{"a"=>0}, {"r"=>17}, {"c"=>2}, {"o"=>14}], [{"u"=>20}, {"s"=>18}, {"i"=>8}, {"n"=>13}], [{"b"=>1}, {"i"=>8}, {"l"=>11}, {"l"=>11}]]
I am planning on adding the values of each letter from each column to get four sums (C1,C2,C3,C4)
So for the first column it would be 12+0+20+1.
This is what I have so far http://repl.it/2cd/1.
Any help on what im doing wrong or a better way to handle this situation?
One way, starting with the message:
msg = "My dear cousin bill!"
arr = msg.downcase.gsub(/[^a-z]/,'').chars.each_slice(4).to_a
#=> [["m", "y", "d", "e"],
# ["a", "r", "c", "o"],
# ["u", "s", "i", "n"],
# ["b", "i", "l", "l"]]
4.times.map { |i| arr.reduce(0) { |t,a| t + (a[i]||?a).ord-?a.ord } }
#=> [33, 67, 24, 42]
msg = "My dearest cousin bill!"
arr = msg.downcase.gsub(/[^a-z]/,'').chars.each_slice(4).to_a
#=> [["m", "y", "d", "e"],
# ["a", "r", "e", "s"],
# ["t", "c", "o", "u"],
# ["s", "i", "n", "b"],
# ["i", "l", "l"]]
4.times.map { |i| arr.reduce(0) { |t,a| t + (a[i]||?a).ord-?a.ord } }
#=>[57, 62, 45, 43]
I would probably go with a slightly different approach:
initial_message = "My dear cousin bill!"
chars = initial_message.tr('^a-zA-Z0-9','').downcase.chars
char_map = ->(char) { char.ord - 97 }
results = chars.each_slice(4).each_slice(4).map do |array|
array.transpose.map {|column| column.reduce(0) {|res, letter| res + char_map[letter]} }
end
results.inspect => '[[33, 67, 24, 42]]'
This is not hitting the intermediate step you described in your question, however is probably a better way to achieve your final result.
In the following code
Module.constants[0..1] # => [:object, :Module]
What does the [0..1] mean here?
0..1 is a range. It's syntactic sugar for the Ruby parser to create a Range object. You can do a lot with ranges, including simple iteration:
irb(main):003:0> (1..3).class
=> Range
irb(main):004:0> (1..3).each {|x| puts x}
1
2
3
=> 1..3
You can turn it into an Array, among other things:
irb(main):005:0> (1..3).to_a
=> [1, 2, 3]
When you use a Range as an Array#[] argument, it means you want all the elements whose index is in that range (inclusive):
irb(main):007:0> stuff = %w{a b c d e f}
=> ["a", "b", "c", "d", "e", "f"]
irb(main):008:0> range = 2..4
=> 2..4
irb(main):009:0> stuff[range]
=> ["c", "d", "e"]
Module.constants returns an array of all the constants defined in (i.e. namespaced to) the Module class (yes, Module is a class; see Module.class). The [0..1] says give me every element of the array from the 0th to the 1st. In general, if x is an array, then x[m..n] returns the subarray of x consisting of the elements from the mth to the nth. For example:
x = [36, 25, 16, 9, 4]
x[1..3] # => [25, 16, 9]
With a list in Python I can return a part of it using the following code:
foo = [1,2,3,4,5,6]
bar = [10,20,30,40,50,60]
half = len(foo) / 2
foobar = foo[:half] + bar[half:]
Since Ruby does everything in arrays I wonder if there is something similar to that.
Yes, Ruby has very similar array-slicing syntax to Python. Here is the ri documentation for the array index method:
--------------------------------------------------------------- Array#[]
array[index] -> obj or nil
array[start, length] -> an_array or nil
array[range] -> an_array or nil
array.slice(index) -> obj or nil
array.slice(start, length) -> an_array or nil
array.slice(range) -> an_array or nil
------------------------------------------------------------------------
Element Reference---Returns the element at index, or returns a
subarray starting at start and continuing for length elements, or
returns a subarray specified by range. Negative indices count
backward from the end of the array (-1 is the last element).
Returns nil if the index (or starting index) are out of range.
a = [ "a", "b", "c", "d", "e" ]
a[2] + a[0] + a[1] #=> "cab"
a[6] #=> nil
a[1, 2] #=> [ "b", "c" ]
a[1..3] #=> [ "b", "c", "d" ]
a[4..7] #=> [ "e" ]
a[6..10] #=> nil
a[-3, 3] #=> [ "c", "d", "e" ]
# special cases
a[5] #=> nil
a[6, 1] #=> nil
a[5, 1] #=> []
a[5..10] #=> []
If you want to split/cut the array on an index i,
arr = arr.drop(i)
> arr = [1,2,3,4,5]
=> [1, 2, 3, 4, 5]
> arr.drop(2)
=> [3, 4, 5]
You can use slice() for this:
>> foo = [1,2,3,4,5,6]
=> [1, 2, 3, 4, 5, 6]
>> bar = [10,20,30,40,50,60]
=> [10, 20, 30, 40, 50, 60]
>> half = foo.length / 2
=> 3
>> foobar = foo.slice(0, half) + bar.slice(half, foo.length)
=> [1, 2, 3, 40, 50, 60]
By the way, to the best of my knowledge, Python "lists" are just efficiently implemented dynamically growing arrays. Insertion at the beginning is in O(n), insertion at the end is amortized O(1), random access is O(1).
Ruby 2.6 Beginless/Endless Ranges
(..1)
# or
(...1)
(1..)
# or
(1...)
[1,2,3,4,5,6][..3]
=> [1, 2, 3, 4]
[1,2,3,4,5,6][...3]
=> [1, 2, 3]
ROLES = %w[superadmin manager admin contact user]
ROLES[ROLES.index('admin')..]
=> ["admin", "contact", "user"]
another way is to use the range method
foo = [1,2,3,4,5,6]
bar = [10,20,30,40,50,60]
a = foo[0...3]
b = bar[3...6]
print a + b
=> [1, 2, 3, 40, 50 , 60]
I like ranges for this:
def first_half(list)
list[0...(list.length / 2)]
end
def last_half(list)
list[(list.length / 2)..list.length]
end
However, be very careful about whether the endpoint is included in your range. This becomes critical on an odd-length list where you need to choose where you're going to break the middle. Otherwise you'll end up double-counting the middle element.
The above example will consistently put the middle element in the last half.