Converting a multi-level hash into a single hash - ruby

I have a source hash:
a = {'1' => 'A',
'2' => 'B',
'3' => 'C',
'4' => { '5' => 'D', '6' => 'E', '7' => { '8' => 'F', '9' => 'G' }},
'10' => {'11' => 'H'}}
I need to construct a method to make it a flat hash (single hash). The result should look like:
a = {'1' => 'A', '2' => 'B', '3' => 'C', '5' => 'D', '6' => 'E', '8' => 'F', '9' => 'G', '11' => 'H'}
I tried with merge, deep_merge, each_with_object, and recursion, but they did not give proper results.
Appreciate any help.

a = {'1' => 'A',
'2' => 'B',
'3' => 'C',
'4' => { '5' => 'D', '6' => 'E', '7' => { '8' => 'F', '9' => 'G' }},
'10' => {'11' => 'H'}}
# recursive detect value is Hash or not
compact_hash = ->(hh, h0={}) {
hh.reduce(h0) do |h, (k, v)|
if v.is_a? Hash
compact_hash[v, h]
else
h[k] = v
h
end
end
}
puts compact_hash[a]
Many thanks to #cary, I knew how to do one-line without ; and remove h.
compact_hash = ->(hh, h0={}) {
hh.each_with_object(h0) { |(k, v), h | v.is_a?(Hash) ? compact_hash[v, h] : h[k] = v }
}

h = { '1' => 'A', '2' => 'B', '3' => 'C',
'4' => { '5' => 'D', '6' => 'E', '7' => { '8' => 'F', '9' => 'G' } },
'10' => { '11' => 'H'} }
hh = h.dup
loop do
g = hh.select { |_,v| v.is_a? Hash }
break hh if g.empty?
g.keys.each { |k| hh.delete(k) }
g.values.each { |f| hh.update(f) }
end
#=> {"1"=>"A", "2"=>"B", "3"=>"C", "5"=>"D", "6"=>"E", "11"=>"H", "8"=>"F", "9"=>"G"}
This does not mutate h:
h #=> { "1"=>"A", "2"=>"B", "3"=>"C",
# "4"=>{"5"=>"D", "6"=>"E", "7"=>{"8"=>"F", "9"=>"G"}},
# "10"=>{"11"=>"H"}}
The antepenultimate line could be replaced by the following.
g_keys = g.keys
hh.delete_if { |k| g_keys.include?(k) }
I don't know which would be the more efficient.

a = {'1' => 'A',
'2' => 'B',
'3' => 'C',
'4' => { '5' => 'D', '6' => 'E', '7' => { '8' => 'F', '9' => 'G' }},
'10' => {'11' => 'H'}}
def flatten_hash(hash)
hash.each_pair.reduce({}) do |h, (k, v)|
v.is_a?(Hash) ? h.merge(flatten_hash(v)) : h.merge(k => v)
end
end
# pry(main)> flatten_hash(a)
#=> {"1"=>"A", "2"=>"B", "3"=>"C", "5"=>"D", "6"=>"E", "8"=>"F", "9"=>"G", "11"=>"H"}

Related

Mistake where it shouldn't be

function generateRandomString($minlen = 7, $maxlen = 10, $randomCase = 0) {
$length = rand($minlen, $maxlen);
$symbols = array('A', 'B', 'C', 'D', 'E', 'F',
'G', 'H', 'I', 'J', 'K', 'L',
'M', 'N', 'O', 'P', 'R', 'S',
'T', 'U', 'V', 'X', 'Y', 'Z',
'1', '2', '3', '4', '5', '6',
'7', '8', '9', '0');
$string = '';
for ($i = 0; $i < $length; $i++) {
$index = rand(0, strlen($symbols) - 1);
$symbol = $symbols[$index];
if ($randomCase)
$symbol = (rand(0, 1)) ? strtolower($symbol) : $symbol;
$string .= $symbol;
}
return $string;
Where can there be an error in the code?

How to insert values in dynamically nested hash?

I have an array of strings of unknown length (but let's say up to 5). I have also an empty hash h = {} and a value.
I want to transform the array and the value to hash like this:
val = 1
h = {}
a = ['a', 'b', 'c', 'd']
# result I want:
{
'a' => {
'b' => {
'c' => {
'd' => 1
}
}
}
}
What's important is that some of the keys might already exist (created in a loop iteration before). So I might have:
val = 2
h = {'a' => {'b' => {'c' => {'d' => 1}}}}
a = ['a', 'b', 'c', 'e']
# result I want:
{
'a' => {
'b' => {
'c' => {
'd' => 1,
'e' => 2
}
}
}
}
Any ideas on how to do that?
Once again, inject to the rescue:
def dredge(hash, list, value = nil)
# Snip off the last element
*list, tail = list
# Iterate through the elements in the path...
final = list.inject(hash) do |h, k|
# ...and populate with a hash if necessary.
h[k] ||= { }
end
# Add on the final value
final[tail] = value
hash
end
This could be improved a little depending on how resilient you need it to be on things like zero-length lists and so on.
Here it is applied:
h = {}
a = ['a', 'b', 'c', 'd']
dredge(h, a, 1)
# => {"a"=>{"b"=>{"c"=>{"d"=>1}}}}
h = {'a' => {'b' => {'c' => {'d' => 1}}}}
a = ['a', 'b', 'c', 'e']
dredge(h, a, 2)
# => {"a"=>{"b"=>{"c"=>{"d"=>1, "e"=>2}}}}

I changed my integers to strings, but #gsub only recognizes 0-9

I'm self-learning Ruby, and one assignment is to make a Caesar cipher.
Using #gsub, I've been able to change my letters to integers ('c' => 2), shift them, then change the new integers to strings (2 => "2").
I've hit a wall, and the Ruby documentation isn't helping. When I try to #gsub the strings back to letters ("2" => 'c') it only recognizes 0-9. Everything after that is just a concatenation of those numbers ("12" => 'bc' instead of => 'l').
Why does Ruby do this, and how can I fix it?
Thanks for your help guys.
code: (I know it's sloppy beginner's code; I will try to edit it after it passes)
def convert_to_integer
puts "What would you like to encode?"
words = gets.chomp
words = words.split("")
words.map { |words| words.gsub!(/[a-z]/, 'a' => 0, 'b' => 1, 'c' => 2, 'd' => 3, 'e' => 4, 'f' => 5, 'g' => 6, 'h' => 7, 'i' => 8, 'j' => 9, 'k' => 10, 'l' => 11, 'm' => 12, 'n' => 13, 'o' => 14, 'p' => 15, 'q' => 16, 'r' => 17, 's' => 18, 't' => 19, 'u' => 20, 'v' => 21, 'w' => 22, 'x' => 23, 'y' => 24, 'z' => 25)
}
integer = words.map! { |letter| letter.to_i }
return integer
end
def shift_left(integer, number = 0)
puts "How many letters (to the left) would you like to shift it?"
number = gets.to_i
integer.map! { |n| n - number }
return integer
end
def convert_to_letter(integer)
integer.map! { |integer| integer.to_s }
integer.map! { |n| n.gsub(/[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 , 11, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25]/, '0' => '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')
}
print integer
end
convert_to_letter(shift_left(convert_to_integer))
You don't need to do a gsub there. gsub is normally used to replace parts of a bigger string. You want to replace the whole thing.
This should do the trick:
def convert_to_letter(integers)
replacements = {0 => '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'
}
integers.map{|x| replacements[x]}.join
end
Also, be careful with destructive operations (map! here). You may run into undesired side-effects (for example, some arrays will change when you think they shouldn't).
It's easier and faster to use lookups:
#letter_to_number = ('a'..'z').zip(0..25).to_h
#number_to_letter = (0..25).zip('a'..'z').to_h
def convert_to_integers(letters)
letters.map{|l| #letter_to_number[l]}
end
def convert_to_letters(numbers)
numbers.map{|n| #number_to_letter[n]}
end
There's also a shortcut that combines the lookups and combines the methods.
#convert = (('a'..'z').zip(0..25) + (0..25).zip('a'..'z')).to_h
def convert(objects)
objects.map{|o| #convert[o]}
end
That's not how regular expressions work. "12".gsub(/[12]/, '12' => 'm') does not produce "m". That code says to find any occurrence of "1" or "2", and replace it according to the following rule: "12" gets replaced with "m", and, implicitly, anything else gets replaced with nothing. Both the "1" and the "2" are occurrences of "1" or "2", but neither of them are "12", so they both get replaced with nothing. Thus the above results in just the empty string.
In fact gsub and regular expressions are not really ideal for this problem. You could just do this:
def char_to_int(char)
char.ord - 97
end
def int_to_char(int)
(int + 97).chr
end
def caesar(string, shift)
string.split(" ").map do |word|
word.split("").map do |letter|
int_to_char((char_to_int(letter) - shift) % 26)
end.join
end.join(" ")
end

Replacing values in a byte type NArray in Ruby

I am looking for a way to replace all occurrences of 'A' with 1, 'T' with 2, 'C' with 8, and 'G' with 16 in a byte array. How can this be done?
require "narray"
class NArray
def cast(type)
a = NArray.new(type,*self.shape)
a[] = self
a
end
end
conv = NArray.int(256)
atcg = NArray.to_na('ATCG', NArray::BYTE).cast(NArray::LINT)
conv[atcg] = [1,2,8,16]
seq_str = 'ABCDAGDE'
seq_ary = NArray.to_na(seq_str, NArray::BYTE).cast(NArray::LINT)
p conv[seq_ary]
#=> NArray.int(8):
# [ 1, 0, 8, 0, 1, 16, 0, 0 ]
Is it what you are looking for?
h = {'A' => 1, 'T' => 2, 'C' => 8, 'G' => 16}
a = ['A', 'B', 'C', 'D', 'A', 'G', 'D', 'E']
result = a.map {|c| h.include?(c) ? h[c] : c }

Parse abbreviated numbers in Ruby

What's the best way in Ruby to turn human-readable abbreviated numbers into actual integers?
Examples:
"1.2M" => 1200000
"477k" => 477000
module SIValue
# http://en.wikipedia.org/wiki/SI_prefix
PREFIX_MAGNITUDES = {
'Y' => 24, 'Z' => 21, 'E' => 18, 'P' => 15, 'T' => 12,
'G' => 9, 'M' => 6, 'k' => 3, 'h' => 2, 'da' => 1,
'd' => -1, 'c' => -2, 'm' => -3, 'μ' => -6, 'n' => -9,
'p' => -12, 'f' => -15, 'a' => -18, 'z' => -21, 'y' => -24
}
def self.from( str )
_, num, prefix = str.match(/^([-+]?[0-9]*\.?[0-9]+(?:[eE][-+]?[0-9]+)?)(#{PREFIX_MAGNITUDES.keys.join('|')})?/o).to_a
if num
prefix ? num.to_f * 10**PREFIX_MAGNITUDES[prefix] : num.to_f
else
0.0
end
end
end
%w[ 1k +3.3m +3.3M 123.123da 0.31h 0.31μ cats ].each do |s|
p [s,SIValue.from(s) ]
end
#=> ["1k", 1000.0]
#=> ["+3.3m", 0.0033]
#=> ["+3.3M", 3300000.0]
#=> ["123.123da", 1231.23]
#=> ["0.31h", 31.0]
#=> ["0.31μ", 3.1e-07]
#=> ["cats", 0.0]
def scale s
a = s.downcase.split /(?=[a-z])/
Integer(a.first.to_f * Hash.new(1).merge('k' => 1024, 'm' => 1024 * 1024)[a[1]] + 0.5)
end
# you may want to extend the hash with more suffix types

Resources