How can I build a hash based in a string in ruby? - ruby

Example -
String1: "{t: 1},{t2: 2}"
String2: "{s: 1},{s2: 2}"
Build an array of hashes which returns
[
{t: 1},
{t2: 2},
{s: 1},
{s2: 2}
]

If you are given an hash:
h = { String1: "{t: 1},{t2: 2}", String2: "{s: 1},{s2: 2}" }
and wish to return an array:
[ {t: 1}, {t2: 2}, {s: 1}, {s2: 2} ]
you can do it thus:
h.values.flat_map do |s|
s.split(',').map do |t|
k,v = t.scan(/\w+/)
{ k.to_sym=>v }
end
end
#=> [{:t=>"1"}, {:t2=>"2"}, {:s=>"1"}, {:s2=>"2"}]
The steps:
a = h.values
#=> ["{t: 1},{t2: 2}", "{s: 1},{s2: 2}"]
enum = a.flat_map
#=> #<Enumerator: ["{t: 1},{t2: 2}", "{s: 1},{s2: 2}"]:flat_map>
The first element of the enumerator is passed to the block:
s = enum.next
#=> "{t: 1},{t2: 2}"
c = s.split(',')
#=> ["{t: 1}", "{t2: 2}"]
map maps each of c's two elements to a hash:
k,v = "{t: 1}".scan(/\w+/)
#=> ["t", "1"]
{ k.to_sym=>v }
#=> {:t=>"1"}
and
k,v = "{t2: 2}".scan(/\w+/)
#=> ["t2", "2"]
{ k.to_sym=>v }
#=> {:t2=>"2"}
The second and final element of enum is now passed to the block:
s = enum.next
#=> "{s: 1},{s2: 2}"
The above steps are repeated to obtain the two hashes {:s=>1} and {:s2=>2}. Since we are using Enumerable#flat_map, we obtain:
[{:t=>"1"}, {:t2=>"2"}, {:s=>"1"}, {:s2=>"2"}]
rather than:
[[{:t=>"1"}, {:t2=>"2"}], [{:s=>"1"}, {:s2=>"2"}]]
which is what we'd get if we used Enumerable#map.

What you are expecting is invalid, as #mudasobwa says.
But what you can do, is use eval to convert it to array of hashes:
a = "{t: 1},{t2: 2}"
#=> "{t: 1},{t2: 2}"
b = "{s: 1},{s2: 2}"
#=> "{s: 1},{s2: 2}"
[a.split(','), b.split(',')].flatten.map{|a| eval a}
#=> [{:t=>1}, {:t2=>2}, {:s=>1}, {:s2=>2}]

Related

Can we do below problem by initializing hash's key with a default value?

Write a method that groups the above hash into 2 groups of 'even' and 'odd' length using 'inject'.
input - "['abc','def','1234','234','abcd','x','mnop','5','zZzZ']"
My code listed below already works. But I want to know better way to do it using default value for hash's key. I meant to say something like below -
h=Hash.new { |hash, key| hash[key] = []}
Solution :
class Array
def group_even_odd
key_hash = group_by(&:length)
key_array = %w(even odd)
key_hash.each_with_object('odd' => [], 'even' => []) do |(key, value), even_odd_hash|
even_odd_hash[key_array[key % 2]].push(value)
even_odd_hash
end
end
end
if ARGV.empty?
puts 'Please provide an input'
else
input = ARGV[0].scan(/\w+/).map(&:to_s)
puts input.group_even_odd
end
Expected and actual are same, code is working.
Expected result -
{"odd"=>[["abc", "def", "234"], ["x", "5"]], "even"=>[["1234", "abcd", "mnop", "zZzZ"]]}
One possible option, given
ary = ["abc", "def", "1234", "234", "abcd", "x", "mnop", "5", "zZzZ"]
First group by even odd, then group by size:
ary.group_by { |e| e.size.even? ? 'even' : 'odd' }
.transform_values { |v| v.group_by(&:size).values }
#= {"odd"=>[["abc", "def", "234"], ["x", "5"]], "even"=>[["1234", "abcd", "mnop", "zZzZ"]]}
First step, to explain:
ary.group_by { |e| e.size.even? ? 'even' : 'odd' }
#=> {"odd"=>["abc", "def", "234", "x", "5"], "even"=>["1234", "abcd", "mnop", "zZzZ"]}
Then Hash#transform_values grouping each by size.
The following does not meet the requirement that inject (aka reduce) be used, but it is how I would do it.
arr = ['abc', 'def', '1234', '234', 'abcd', 'x', 'mnop', '5', 'zZzZ']
odd, even = arr.each_with_object(Hash.new { |h,k| h[k]=[] }) do |s,h|
h[s.size] << s
end.
values.
partition { |a| a.first.size.odd? }
#=> [[["abc", "def", "234"], ["x", "5"]],
# [["1234", "abcd", "mnop", "zZzZ"]]]
{ "odd"=>odd, "even"=>even }
#=> {"odd"=>[["abc", "def", "234"], ["x", "5"]],
# "even"=>[["1234", "abcd", "mnop", "zZzZ"]]}
The steps are as follows.
h = arr.each_with_object(Hash.new {|h,k| h[k]=[]}) do |s,h|
h[s.size] << s
end
#=> {3=>["abc", "def", "234"], 4=>["1234", "abcd", "mnop", "zZzZ"],
# 1=>["x", "5"]}
a = h.values
#=> [["abc", "def", "234"], ["1234", "abcd", "mnop", "zZzZ"],
# ["x", "5"]]
odd, even = a.partition { |a| a.first.size.odd? }
#=> [[["abc", "def", "234"], ["x", "5"]],
# [["1234", "abcd", "mnop", "zZzZ"]]]
{ "odd"=>odd, "even"=>even }
#=> {"odd"=>[["abc", "def", "234"], ["x", "5"]],
# "even"=>[["1234", "abcd", "mnop", "zZzZ"]]}
If one insists on fitting a square peg into a round hold (using inject/reduce), I suppose that could be done as follows.
arr.reduce({ "odd"=>[], "even"=>[] }) do |g,s|
oe = s.size.odd? ? "odd" : "even"
i = g[oe].find_index { |a| a.any? && a.first.size == s.size }
case i.nil?
when true then g[oe] << [s]
else g[oe][i] << s
end
g
end
#=> {"odd"=>[["abc", "def", "234"], ["x", "5"]],
# "even"=>[["1234", "abcd", "mnop", "zZzZ"]]}

How to convert a string of values with a range to an array in Ruby

I'm trying to parse a string of numbers and ranges joined by ",", and convert it to a numerical array. I have this as input: "1,3,6-8,5", and would like to have an array like this: [1,3,5,6,7,8].
I can only do it without the range, like this:
"12,2,6".split(",").map { |s| s.to_i }.sort #=> [2, 6, 12]
With a range, I cannot do it:
a = "12,3-5,2,6"
b = a.gsub(/-/, "..") #=> "12,3..5,2,6"
c = b.split(",") #=> ["12", "3..5", "2", "6"]
d = c.sort_by(&:to_i) #=> ["2", "3..5", "6", "12"]
e = d.split(",").map { |s| s.to_i } #>> Error
How can I do this?
I was also thinking to use the splat operator in map, but splat doesn't accept strings like [*(3..5)].
"12,3-5,2,6".
gsub(/(\d+)-(\d+)/) { ($1..$2).to_a.join(',') }.
split(',').
map(&:to_i)
#⇒ [12, 3, 4, 5, 2, 6]
"1,3,6-8,5".split(',').map do |str|
if matched = str.match(/(\d+)\-(\d+)/)
(matched[1].to_i..matched[2].to_i).to_a
else
str.to_i
end
end.flatten
or
"1,3,6-8,5".split(',').each_with_object([]) do |str, output|
if matched = str.match(/(\d+)\-(\d+)/)
output.concat (matched[1].to_i..matched[2].to_i).to_a
else
output << str.to_i
end
end
or strict
RANGE_PATTERN = /\A(\d+)\-(\d+)\z/
INT_PATTERN = /\A\d+\z/
"1,3,6-8,5".split(',').each_with_object([]) do |str, output|
if matched = str.match(RANGE_PATTERN)
output.concat (matched[1].to_i..matched[2].to_i).to_a
elsif str.match(INT_PATTERN)
output << str.to_i
else
raise 'Wrong format given'
end
end
"1,3,6-8,5".split(',').flat_map do |s|
if s.include?('-')
f,l = s.split('-').map(&:to_i)
(f..l).to_a
else
s.to_i
end
end.sort
#=> [1, 3, 5, 6, 7, 8]
"1,3,6-8,5"
.scan(/(\d+)\-(\d+)|(\d+)/)
.flat_map{|low, high, num| num&.to_i || (low.to_i..high.to_i).to_a}
#=> [1, 3, 6, 7, 8, 5]

Ruby - converting a string into hash with each character as key and index as value?

I am trying to transform a given string into a hash with each its character = key and index = value.
For example, if I have str = "hello", I would like it to transform into {"h"=>0, "e"=>1, "l"=>2, "l"=>3, "o"=>4}.
I created a method as such:
def map_indices(arr)
arr.map.with_index {|el, index| [el, index]}.to_h
end
#=> map_indices('hello'.split(''))
#=> {"h"=>0, "e"=>1, "l"=>3, "o"=>4}
The problem is it skips the first l. If I reverse the order of el and index: arr.map.with_index {|el, index| [index, el]}.to_h, I get all the letters spelled out: {0=>"h", 1=>"e", 2=>"l", 3=>"l", 4=>"o"}
But when I invert it, I get the same hash that skips one of the l's.
map_indices('hello'.split('')).invert
#=> {"h"=>0, "e"=>1, "l"=>3, "o"=>4}
Why is this behaving like such? How can I get it to print {"h"=>0, "e"=>1, "l"=>2, "l"=>3, "o"=>4}?
It can be done, but will confuse other Ruby programmers.A normal hash treats a key "a" as identical to another "a". Unless a little known feature .compare_by_identity is used:
h = {}.compare_by_identity
"hello".chars.each_with_index{|c,i| h[c] = i}
p h # => {"h"=>0, "e"=>1, "l"=>2, "l"=>3, "o"=>4}
Any of the following could be used. For
str = "hello"
all return
{"h"=>[0], "e"=>[1], "l"=>[2, 3], "o"=>[4]}
str.each_char
.with_index
.with_object({}) { |(c,i),h| (h[c] ||= []) << i }
See String#each_char, Enumerator#with_index and Enumerator#with_object. The block variables have been written to exploit array decomposition.
str.each_char
.with_index
.with_object(Hash.new { |h,k| h[k] = [] }) { |(c,i),h| h[c] << i }
See the form of Hash::new that takes a block and no argument. If a hash has been defined
h = Hash.new { |h,k| h[k] = [] }
and later
h[c] << i
is executed, h[c] is first set equal to an empty array if h does not have a key c.
str.size
.times
.with_object(Hash.new { |h,k| h[k] = [] }) { |i,h| h[str[i]] << i }
str.each_char
.with_index
.group_by(&:first)
.transform_values { |a| a.flat_map(&:last) }
See Enumerable#group_by, Hash#transform_values (introduced in Ruby v2.5) and Enumerable#flat_map.
Note that
str.each_char
.with_index
.group_by(&:first)
#=> {"h"=>[["h", 0]], "e"=>[["e", 1]], "l"=>[["l", 2], ["l", 3]],
# "o"=>[["o", 4]]}
Another option you can use is zipping two enumerations together.
s = "hello"
s.chars.zip(0..s.size)
This yields: [["h", 0], ["e", 1], ["l", 2], ["l", 3], ["o", 4]]
I am new to Ruby and I am sure this can be refactored, but another alternative might be:
arr1 = "Hello".split(%r{\s*})
arr2 = []
for i in 0..arr1.size - 1
arr2 << i
end
o = arr1.zip(arr2)
a_h = []
o.each do |i|
a_h << Hash[*i]
end
p a_h.each_with_object({}) { |k, v| k.each { |kk,vv| (v[kk] ||= []) << vv } }
=> {"H"=>[0], "e"=>[1], "l"=>[2, 3], "o"=>[4]}

How can I convert a array with repeat value to hash in ruby?

What is the easiest way to do the following conversion.
[#Obj<name: "a", value: 1>, #Obj<name: "a", value: 2>, #Obj<name: "a", value: 3>, #Obj<name: "b", value: 1>, #Obj<name: "b", value: 2>]
=>
{
"a"=> [1, 2, 3],
"b"=> [1, 2]
}
Perhaps using inject to process the list. The methods inject and reduce are equivalent (one is the alias of the other).
hash = my_arr.inject({}) do |h, o|
(h[o.name] ||= []) << o.value
h
end
This should give you the results you're looking for.
You can do it like this:
array = [#Obj<name: "a", value: 1>, #Obj<name: "a", value: 2>, #Obj<name: "a", value: 3>, #Obj<name: "b", value: 1>, #Obj<name: "b", value: 2>]
hash = Hash.new {|h,k| h[k] = []}
array.each {|e| hash[e.name] << e.value}
Alternatively, if you like loops:
for e in array
(h ||= Hash.new {|h,k| h[k] = [] })[e.name] << e.value
end
You can do as below using each_with_object :
arry.each_with_object(Hash.new { |h,k| h[k] = [] }) do |ob,hash|
hash[ob.name] << ob.value
end
Use Enumerable#reduce. E.g. if arr is your array:
arr.reduce(Hash.new{|h, k| h[k] = []}) do |hash, obj|
hash[obj.name] << obj.value
hash
end

Filling wildcard values in a hash with every possible value

Given a set of possible values, and a hash of arbitrary number of values, how can I replace every nil value with every possible combination of the possible values?
For example:
values = %w[a b c]
hash = { x:1, y:2, z:nil }
fill_wildcards( hash, values )
#=> [{ x:1, y:2, z:'a' },
#=> { x:1, y:2, z:'b' },
#=> { x:1, y:2, z:'c' }]
hash = { x:1, y:nil, z:nil }
fill_wildcards( hash, values )
#=> [{ x:1, y:'a', z:'a' },
#=> { x:1, y:'a', z:'b' },
#=> { x:1, y:'a', z:'c' },
#=> { x:1, y:'b', z:'a' },
#=> { x:1, y:'b', z:'b' },
#=> { x:1, y:'b', z:'c' },
#=> { x:1, y:'c', z:'a' },
#=> { x:1, y:'c', z:'b' },
#=> { x:1, y:'c', z:'c' }]
I can find the keys that need to be replaced:
wildkeys = hash.select{ |k,v| v.nil? }.map(&:first)
#=> [:y, :z]
And thus I can find all the permutations of values needed:
wildvalues = values.repeated_permutation( wildkeys.length ).to_a
#=> [["a", "a"], ["a", "b"], ["a", "c"], ["b", "a"],
#=> ["b", "b"], ["b", "c"], ["c", "a"], ["c", "b"], ["c", "c"]]
But I can't think of a simple way to merge these two into the original.
Might be something like this:
rest = hash.reject { |k,v| v.nil? }.to_a
wildvalues.map { |wv| Hash[rest + wildkeys.zip(wv)] }
or even
wildvalues.map { |wv| hash.merge(Hash[wildkeys.zip(wv)]) }
def fill_wildcards( hsh, values )
values.repeated_permutation(hsh.values.count(nil)).to_a.map {|combo| hsh.each_with_object(hsh.dup) {|(k,v),hsh| hsh[k] = combo.shift unless v } }
end
Another way:
Code
def doit(hash, values)
a = hash.map { |k,v| [k].product(v ? [v] : values) }
a.shift.product(*a).map(&:to_h)
end
Demo
values = %w[a b c]
hash = { x:1, y:2, z:nil }
p doit(hash, values)
#=> [{:x=>1, :y=>2, :z=>"a"},
# {:x=>1, :y=>2, :z=>"b"},
# {:x=>1, :y=>2, :z=>"c"}]
hash = { x:1, y:nil, z:nil }
p doit(hash, values)
#=> [{:x=>1, :y=>"a", :z=>"a"},
# {:x=>1, :y=>"a", :z=>"b"},
# {:x=>1, :y=>"a", :z=>"c"},
# {:x=>1, :y=>"b", :z=>"a"},
# {:x=>1, :y=>"b", :z=>"b"},
# {:x=>1, :y=>"b", :z=>"c"},
# {:x=>1, :y=>"c", :z=>"a"},
# {:x=>1, :y=>"c", :z=>"b"},
# {:x=>1, :y=>"c", :z=>"c"}]
hash = { x:nil, y:nil, z:nil }
p doit(hash, values).size #=> 27

Resources