Convert string through hash - arrays

I'm trying to convert a string of morse code into words. I split the string into words. Then I split each word into a secondary array of letters and numbers.
def self.decode(str)
decoded = ""
arr = []
p arr = str.split(' ')
p arr.map! { |i| i.split(' ') }
arr.each do |r|
r.each do |i|
decoded += #morse.invert[i].to_s[-1]
end
end
p decoded
end
In my hash, I use numbers. They start with N. In to_s[-1], the -1 is to drop the N.
I get this error:
`+': no implicit conversion of nil into String
I can figure out how to get past it though because I don't see a nil value in the array.
class Morse
#morse = {
A: '.-',
B: '-...',
C: '-.-.',
D: '-..',
E: '.',
F: '..-.',
G: '--.',
H: '....',
I: '..',
J: '.---',
K: '-.-',
L: '.-..',
M: '--',
N: '-.',
O: '---',
P: '.--.',
Q: '--.-',
R: '.-.',
S: '...',
T: '-',
U: '..-',
V: '...-',
W: '.--',
X: '-..-',
Y: '-.--',
Z: '--..',
' ': ' ' ,
N1: '.----',
N2: '..---',
N3: '...--',
N4: '....-',
N5: '.....',
N6: '-....',
N7: '--...',
N8: '---..',
N9: '----.',
N0: '-----'
}
def self.encode(str)
encoded = ""
sym_temp = ""
str = str.upcase!
str.each_char do |c|
ascii_check = c.ord
if ascii_check.between?(65,90)
temp = str[c].to_sym
encoded += "#{#morse[temp]}" + " "
elsif ascii_check.between?(48,57)
temp = "N".concat(str[c]).to_sym
encoded += "#{#morse[temp]}" + " "
elsif ascii_check ===(32)
temp = str[c].to_sym
encoded += "#{#morse[temp]}"
end
end
p encoded
end
def self.decode(str)
decoded = ""
arr = []
# str.split(' ').map do |i|
p arr = str.split(' ')
p arr.map! { |i| i.split(' ') }
arr.each do |r|
r.each do |i|
p decoded += #morse.invert[i].to_s[-1].to_s
end
end
p decoded
end
# def self.read_file
# # #Temp = File.read("preamble_encode.txt").chomp
# # File.open('hiscore.txt'){|f| f.lines.map{|l| l.chomp!.split(',')}}
# # #Temp = File.foreach("preamble_encode.txt", 'r') {|f| f.lines.map{|l| l.chomp}}
# end
# def self.write_file
# # Temp2 = File.open('preamble_decode.txt', 'w') do |f|
# # f.puts Temp2
# # end
# end
end
test = "Abc 1oL!"
test2 = ".-- . - .... . .--. . --- .--. .-.. ."
Morse.encode(test)
Morse.decode(test2)

Your problem is here:
:P => :'.--.',
Your colon on the right hand side of the hash rocket is making that a symbol, not a string. As it happens, your test case for decode includes a P. When you try to look up the String '.--.', it doesn't find it and returns nil.

As you question has been answered, I would like to suggest how you may write methods for coding and decoding.
Firstly, the keys of your hash should be strings rather than symbols, as the hash is being used to code strings, which are made up of single-character strings. We also need the digits to be, '0' to '9' rather than 'N0' to 'N9'. Spaces are represented by time delays equal to seven dots, rather than combinations of dots and dashes, so I've represented spaces by the string '<delay>'. See the Morse Wiki.
Since the hash won't change, I've made it a constant rather than the value of an instance variable.
CharactersToMorse = {
'A' => '.-', 'B' => '-...', 'C' => '-.-.', 'D' => '-..', 'E' => '.', 'F' => '..-.',
'G' => '--.', 'H' => '....', 'I' => '..', 'J' => '.---', 'K' => '-.-', 'L' => '.-..',
'M' => '--' , 'N' => '-.', 'O' => '---', 'P' => '.--.', 'Q' => '--.-', 'R' => '.-.',
'S' => '...', 'T' => '-', 'U' => '..-', 'V' => '...-', 'W' => '.--', 'X' => '-..-',
'Y' => '-.--', 'Z' => '--..' , ' ' => '<delay>', '0' => '-----', '1' => '.----',
'2' => '..---', '3' => '...--', '4' => '....-', '5' => '.....', '6' => '-....',
'7' => '--...', '8' => '---..', '9' => '----.'
}
We will need to decode, so I'll assign the inverted hash to a constant as well.
MorseToCharacters = CharactersToMorse.invert
#=> {".-"=>"A", "-..."=>"B", "-.-."=>"C", "-.."=>"D", "."=>"E", "..-."=>"F",
# "--."=>"G", "...."=>"H", ".."=>"I", ".---"=>"J", "-.-"=>"K", ".-.."=>"L",
# "--"=>"M", "-."=>"N", "---"=>"O", ".--."=>"P", "--.-"=>"Q", ".-."=>"R",
# "..."=>"S", "-"=>"T", "..-"=>"U", "...-"=>"V", ".--"=>"W", "-..-"=>"X",
# "-.--"=>"Y", "--.."=>"Z", "<delay>"=>" ", "-----"=>"0", ".----"=>"1", "..---"=>"2",
# "...--"=>"3", "....-"=>"4", "....."=>"5", "-...."=>"6", "--..."=>"7",
# "---.."=>"8", "----."=>"9"}
Our methods for coding and decoding are now very simple.
def code(text)
text.each_char.map { |c| CharactersToMorse[c] }
end
def decode(morse)
morse.map { |m| MorseToCharacters[m] }.join(' ')
end
Let's try it.
text = "NOW IS THE TIME FOR 47 RUBIESTS TO COME TO THE AID OF THEIR BOWLING TEAM"
morse = code(text)
#=> ["-.", "---", ".--", "<delay>", "..", "...", "<delay>", "-", "....",
# ".", "<delay>", "-", "..", "--", ".", "<delay>", "..-.", "---", ".-.",
# "<delay>", "....-", "--...", "<delay>", ".-.", "..-", "-...", "..",
# ".", "...", "-", "...", "<delay>", "-", "---", "<delay>", "-.-.", "---",
# "--", ".", "<delay>", "-", "---", "<delay>", "-", "....", ".", "<delay>",
# ".-", "..", "-..", "<delay>", "---", "..-.", "<delay>", "-", "....",
# ".", "..", ".-.", "<delay>", "-...", "---", ".--", ".-..", "..", "-.",
# "--.", "<delay>", "-", ".", ".-", "--"]
decode(morse)
#=> "NOW IS THE TIME FOR 47 RUBIESTS TO COME TO THE AID OF THEIR BOWLING TEAM"
If for some reason the keys of CharactersToMorse must be symbols, change the methods to:
def code(text)
text.each_char.map { |c| CharactersToMorse[c.to_sym] }
end
def decode(morse)
morse.map { |m| MorseToCharacters[m].to_s }.join(' ')
end

Related

compare two array of hash in general without any specific key in ruby

I wanted to compare 2 arrays of hashes for all values without any specific key "d". Please help in comparing 2 arrays of hashes in a way which iterates all values in the array of hashes.
A = [
{​​​​​​​​​​​"a"=>"1000", "b"=>"0", "c"=>"3", "d"=>"1", "e"=>"3", "f"=>"status"}​​​​​​​​​​​,
{​​​​​​​​​​​"a"=>"2", "e"=>"0", "b"=>"0", "c"=>"4", "d"=>"3", "e="3","f"=>"s-2"}​​​​​​​​​​​,
{​​​​​​​​​​​"a"=>"0", "b"=>"0", "c"=>"1", "d"=>"3", "e"=>"1", "f"=>"s-01"}​​​​​​​​​​ ]
B= [
{​​​​​​​​​​​"a"=>"1000", "b"=>"0", "c"=>"3", "d"=>"1", "e"=>"3", "f"=>"status"}​​​​​​​​​​​,
{​​​​​​​​​​​"a"=>"2", "e"=>"0", "b"=>"0", "c"=>"4", "d"=>"3", "e="3","f"=>"s-2"}​​​​​​​​​​​,
{​​​​​​​​​​​"a"=>"0", "b"=>"0", "c"=>"1", "d"=>"3", "e"=>"1", "f"=>"s-01"}​​​​​​​​​​ ]
I have tried below code but I wanted to compare all elements without the specific key "d". Please help!
A.each do |e_hash|
B.each do |a_hash|
if (a_hash["d"].to_s == e_hash["d"].to_s)
e_hash.each do |k,v|
puts v if k == "d" if (a_hash[k].to_s != v.to_s)
count += 1
else
count
end
end
end
When you want to compare two hashes without a specific key then you can use
hash_1 = { "a" => 0, "b" => 0, "c" => 0 }
hash_2 = { "a" => 0, "b" => 0, "c" => 1 }
hash_1.tap { |hs| hs.delete("c") } == hash_2.tap { |hs| hs.delete("c") }
#=> true
In your above example, you could do this:
A = [
{"a"=>"1000", "b"=>"0", "c"=>"3", "d"=>"1", "e"=>"3", "f"=>"status"},
{"a"=>"2", "e"=>"0", "b"=>"0", "c"=>"4", "d"=>"3", "e"=>"3", "f"=>"s-2"},
{"a"=>"0", "b"=>"0", "c"=>"1", "d"=>"3", "e"=>"1", "f"=>"s-01"}
]
B = [
{"a"=>"1000", "b"=>"0", "c"=>"3", "d"=>"1", "e"=>"3", "f"=>"status"},
{"a"=>"2", "e"=>"0", "b"=>"0", "c"=>"4", "d"=>"3", "e"=>"3", "f"=>"s-2"},
{"a"=>"0", "b"=>"0", "c"=>"1", "d"=>"3", "e"=>"1", "f"=>"s-01"}
]
A.each do |e_hash|
B.each do |a_hash|
if e_hash.tap { |hs| hs.delete("d") } == a_hash.tap { |hs| hs.delete("d") }
puts "Equal hash found: #{e_hash}"
end
end
end
#=> Equal hash found: {"a"=>"1000", "b"=>"0", "c"=>"3", "e"=>"3", "f"=>"status"}
# Equal hash found: {"a"=>"2", "e"=>"3", "b"=>"0", "c"=>"4", "f"=>"s-2"}
# Equal hash found: {"a"=>"0", "b"=>"0", "c"=>"1", "e"=>"1", "f"=>"s-01"}

How to convert array like this ["John,Doe,11222019", "Mark,King,11232019", "Angle,Darma,11242019"] to Array of hash Ruby

How to convert array:
["John,Doe,11222019", "Mark,King,11232019", "Angle,Darma,11242019"]
to Array of hash like this using Ruby :
[
{ :name => "John Doe", :number => 11222019 },
{ :name => "Mark King", :number => 11232019 },
{ :name => "Angle Darma", :number => 11242019 },
]
Thank you very much!
You can do it simply as below,
array.map { |x| [:name, :number].zip(x.sub(',', ' ').split(',')).to_h }
# => [{:name=>"John Doe", :number=>11222019}, {:name=>"Mark King", :number=>11232019}, {:name=>"Angle Darma", :number=>11242019}]
Option using Ruby 2.6.1 Object#then:
ary = ["John,Doe,11222019", "Mark,King,11232019", "Angle,Darma,11242019"]
ary.map { |s| s.split(',').then{ |a| {name: a.first(2).join(' ') , number: a.last.to_i } } }
For Ruby 2.5.2 Object#yield_self:
ary.map { |s| s.split(',').yield_self{ |a| {name: a.first(2).join(' ') , number: a.last.to_i } } }
Both returning:
#=> [{:name=>"John Doe", :number=>11222019}, {:name=>"Mark King", :number=>11232019}, {:name=>"Angle Darma", :number=>11242019}]
arr = ["John,Doe,11222019", "Mark,King,11232019",
"Angle,Darma,11242019"]
arr.map do |s|
{name: s[/.+(?=,)/].tr(',',' '), number: s[/\d+/].to_i}
end
#=> [{:name=>"John Doe", :number=>11222019},
# {:name=>"Mark King", :number=>11232019},
# {:name=>"Angle Darma", :number=>11242019}]
The steps are as follows. Enumerable#map passes the first element of arr to the block and the block variable s is set equal to its value:
s = arr.first
#=> "John,Doe,11222019"
The block calculations are then performed:
a = s[/.+(?=,)/]
#=> "John,Doe"
This uses the method String#[] with the regular expression /.+(?=,)/. (?=,) is a positive lookahead that requires the match to be followed immediately by a comma. Because matches are by default greedy the lookahead matches the last comma in s.
b = a.tr(',',' ')
#=> "John Doe"
This uses the method String#tr. Alternatively, a.sub(',',' ') could be used.
c = s[/\d+/]
#=> "11222019"
d = c.to_i
#=> 11222019
The block then returns:
{ name: c, number: d }
#=> {:name=>"11222019", :number=>11222019}
which is the object to which s is mapped. The remaining two values of arr are passed to the block and similar calculations are performed.
a = ["John,Doe,11222019", "Mark,King,11232019", "Angle,Darma,11242019"]
Something like this
a.map do |f|
f = f.split(',')
{ name: "#{f[0]} #{f[1]}", number: f[2].to_i }
end
arr = ["John,Doe,11222019", "Mark,King,11232019", "Angle,Darma,11242019"]
arr.map do |item|
chunks = item.split(",")
{name: chunks[0...-1].join(" "), number: chunks[-1]}
end
Indexing by [0...-1] allows you to have variable number of items in the name part (middle name, or 2 piece last names) which is pretty common.

Concatenating existing values within the same array in Ruby

Here's an example of an array I'm working with:
arr = [ "id1", "name1", "id2", "name2", "id3", "name3" ]
I want to change it into a new array that looks like:
new_arr = [ "id1: name1", "id2: name2", "id3: name3" ]
My attempt:
ids = arr.select.with_index { |_, i| i.even? }
names = arr.select.with_index { |_, i| i.odd? }
new_arr = ids.map { |i| i + ": " + names[ids.index(i)] }
Is there a better or more expressive way of doing this (potentially in a one-liner)?
I would use each_slice and string interpolation.
arr.each_slice(2).map { |(a, b)| "#{a}: #{b}" }
#=> ["id1: name1", "id2: name2", "id3: name3"]
Richard Hamilton`s comment made me think about the performance of the different solutions:
require 'benchmark'
arr = [ "id1", "name1", "id2", "name2", "id3", "name3" ]
slices = arr.each_slice(2)
n = 1_000_000
Benchmark.bmbm(15) do |x|
x.report("hashified :") { n.times do; Hash[*arr].map { |e| e.join ': ' } ; end }
x.report("concatenation :") { n.times do; slices.map { |a| a[0] + ": " + a[1] } ; end }
x.report("array join :") { n.times do; slices.map { |a| a.join(': ') } ; end }
x.report("interpolated :") { n.times do; slices.map { |(a, b)| "#{a}: #{b}" } ; end }
end
# Rehearsal ---------------------------------------------------
# hashified : 3.520000 0.030000 3.550000 ( 3.561024)
# concatenation : 2.300000 0.010000 2.310000 ( 2.315952)
# array join : 3.020000 0.010000 3.030000 ( 3.032235)
# interpolated : 1.950000 0.000000 1.950000 ( 1.954937)
# ----------------------------------------- total: 10.840000sec
#
# user system total real
# hashified : 3.430000 0.040000 3.470000 ( 3.473274)
# concatenation : 2.320000 0.010000 2.330000 ( 2.332920)
# array join : 3.070000 0.010000 3.080000 ( 3.081937)
# interpolated : 1.950000 0.000000 1.950000 ( 1.956998)
You can use Enumerable's each_slice method to get an enumeration of 2-element arrays from your arr. You can then simply join these elements:
arr.each_slice(2).map{|a| a.join(': ')}
What happens here is the each_slice returns an Enumerator which yields the 2-element arrays. Since Enumerators also are Enumerable, you can just use map to change these 2-element arrays and join them to a String.
each_slice is silly :)
Hash[ "id1", "name1", "id2", "name2", "id3", "name3" ].
map { |e| e.join ': ' }
#⇒ [ "id1: name1", "id2: name2", "id3: name3" ]
Try using each_slice:
arr.each_slice(2).entries.map { |ar| ar.join(': ') }
#=> ["id1: name1", "id2: name2", "id3: name3"]
You should use each_slice for this
arr.each_slice(2).map { |a| a[0] + ": " + a[1] }
=> ["id1: name1", "id2: name2", "id3: name3"]

Converting array of stringified key value pairs to hash in Ruby

I have some key-value pair strings in an array:
array = [ "Name = abc", "Id = 123", "Interest = Rock Climbing" ]
I need to convert it to a hash:
hash = { "Name" => "abc", "Id" => "123", "Interest" => "Rock Climbing" }
I must be doing something wrong because I'm getting weird mappings with my .shift.split resulting in {"Name=abc"=>"Id=123"}.
All you need to do is split each part of the array into a key and value (yielding an array of two-element arrays) and then pass the result to the handy Hash[] method:
arr = [ "Name = abc", "Id = 123", "Interest = Rock Climbing" ]
keys_values = arr.map {|item| item.split /\s*=\s*/ }
# => [ [ "Name", "abc" ],
# [ "Id", "123" ],
# [ "Interest", "Rock Climbing" ] ]
hsh = Hash[keys_values]
# => { "Name" => "abc",
# "Id" => "123",
# "Interest" => "Rock Climbing" }
You can do it this way (using Enumerable#each_with_object):
array.each_with_object({}) do |a, hash|
key,value = a.split(/\s*=\s*/) # splitting the array items into key and value
hash[key] = value # storing key => value pairs in the hash
end
# => {"Name"=>"abc", "Id"=>"123", "Interest"=>"Rock Climbing"}
If you find it little difficult to understand the each_with_object, you can do it in a naive way (Just accumulating the key and values in the result_hash):
result_hash = {}
array.each do |a|
key,value = a.split(/\s*=\s*/) # splitting the array items into key and value
result_hash[key] = value # storing key => value pairs in the result_hash
end
result_hash
# => {"Name"=>"abc", "Id"=>"123", "Interest"=>"Rock Climbing"}
Try this
array.map {|s| s.split('=')}.to_h
=> {"Name "=>" abc", "Id "=>" 123", "Interest "=>" Rock Climbing"}
array.each_with_object({}) { |s,h| h.update([s.split(/\s*=\s*/)].to_h) }
#=> {"Name"=>"abc", "Id"=>"123", "Interest"=>"Rock Climbing"}
For Ruby versions prior to 2.0 (when Array#to_h was introduced) replace [s.split(/\s*=\s*/)].h with Hash[[s.split(/\s*=\s*/)]].
The steps:
enum = array.each_with_object({})
#=> #<Enumerator: ["Name = abc", "Id = 123",
# "Interest = Rock Climbing"]:each_with_object({})>
We can see the elements of this enumerator by converting it to an array:
enum.to_a
#=> [["Name = abc", {}], ["Id = 123", {}], ["Interest = Rock Climbing", {}]]
The first element of enum is passed to the block, the block variables are assigned:
s,h = enum.next
#=> ["Name = abc", {}]
s #=> "Name = abc"
h #=> {}
and the block calculation is performed:
h.update([s.split(/\s*=\s*/)].to_h)
#=> h.update([["Name", "abc"]].to_h)
# {}.update({"Name"=>"abc"})
# {"Name"=>"abc"}
which is the updated value of h.
The next element of enum passed to the block is:
s,h = enum.next
#=> ["Id = 123", {"Name"=>"abc"}]
s #=> "Id = 123"
h #=> {"Name"=>"abc"}
h.update([s.split(/\s*=\s*/)].to_h)
#=> {"Name"=>"abc", "Id"=>"123"}
and so on.

Array to Hash with Ruby

I am trying to convert this array:
["dog", 5 , "big house"]
to hash:
{"dog" => 3 , 5 => 25, "big house" => 9}
The value will be the number of characters of the string (key).
If it's an integer (key), then the value will be to the power of 2.
This is what I got so far, but it only converts the string (key):
h = {}
arr.each do |x,y|
y = x.length
h[x] = y
end
▶ arr = ["dog", 5, "big house"]
#⇒ [ "dog", 5, "big house" ]
▶ arr.zip(arr.map do |e|
▷ case e
▷ when String then e.length
▷ when Integer then e ** 2
▷ else raise 'Neither string nor numeric given'
▷ end
▷ end).to_h
#⇒ { "dog" => 3, 5 => 25, "big house" => 9 }
You could use the Enumberable#each_with_object method like so:
array = ["dog",5,"big house"]
array.each_with_object({}) {|x,hash| x.is_a?(String) ? hash[x] = x.length : hash[x] = x**2}
# => {"dog"=>3,5=>25,"big house"=>9}
The each_with_object method is very similar to the inject method, so it'll iterate through the array, then once completed it'll return the newly given object. The difference between each_with_object and inject is that each_with_object is immutable, so you can't do something like:
(1..5).each_with_object(0) {|num,sum| sum += num}
It'll only return a zero.
You can use the Hash[...] constructor to convert an array of [key, value] pairs to a Hash. So here's another option:
arr = ["dog", 5, "big house"]
result = Hash[ arr.map { |e| [e, e.to_i == e ? e**2 : e.length] } ]
# => {"dog"=>3, 5=>25, "big house"=>9}
Since Ruby 2, you can use Array#to_h to do the same thing:
result = arr.map { |e| [e, e.to_i == e ? e**2 : e.length] }.to_h
# => {"dog"=>3, 5=>25, "big house"=>9}
If you have the following array:
arr = ["dog", 5, "big house"]
First you can create a method to convert elements into your desired format based on the element's class:
def convert_to_hash_val(element)
case element
when Fixnum
element**2
else
element.size
end
end
Then create an array of [element, converted_element] pairs and use the Hash#[] function to convert this to key, value pairs:
Hash[ arr.map {|element| [element, convert_to_hash_val(element)]} ]
# => { "dog" => 3, 5 => 25, "big house" => 9 }
You can use inject method.
is_a? method can judge the element is String or Integer.
["dog", 2 , "big house"].inject({}) do |sum, e|
if e.is_a? Integer
sum.merge({e => e*e})
elsif e.is_a? String
sum.merge({e => e.length})
end
end
=> {"dog"=>3, 2=>4, "big house"=>9}
as #Myst said, h[:key] = value has better performance than merge, so you also can do like this:
["dog", 2 , "big house"].inject({}) do |sum, e|
if e.is_a? Integer
sum[e] = e*e
elsif e.is_a? String
sum[e] = e.length
end
sum
end
arr= ["dog", 5, "big house"]
two_dimensional_array = arr.zip(arr.map do |e|
case e
when String then e.length
when Integer then e ** 2
else raise 'Neither string nor numeric given'
end
end)
my_hash = Hash[*two_dimensional_array.flatten]
result_hash = {}
arr = ["dog", 2, "big house"]
arr.each do |el|
if el.is_a?(String)
result_hash[el] = el.length
elsif el.is_a?(Fixnum)
result_hash[el] = el ** 2
end
end
Remember that the each block called on an array only throws one argument into the block, which is the currently iterated element.

Resources