unable to combine 2 arrays into a hash - ruby - ruby

I’m having a few problems creating a hash out of 2 arrays in ruby (1.9.2)
My issue is some of the hash keys are the same and it seems to cause an issue
So my first array (called listkey) contains these 5 items
puts listkey
service_monitor_errorlog
service_monitor_errorlog
wmt_errorlog
wmt_errorlog
syslog
the second ( called listvalue) contains these 5 items
puts listvalue
service_monitor_errorlog.0
service_monitor_errorlog.current
wmt_errorlog.0
wmt_errorlog.current
syslog.txt
what I want is a hash which contains all 5 items e.g.
{
"service_monitor_errorlog"=>"service_monitor_errorlog.0",
"service_monitor_errorlog"=>"service_monitor_errorlog.current",
"wmt_errorlog"=>"wmt_errorlog.0",
"wmt_errorlog"=>"wmt_errorlog.current",
"syslog"=>"syslog.txt"
}
However using the hash zip command
MyHash = Hash[listkey.zip(listvalue)]
I get this hash produced
puts MyHash
{
"service_monitor_errorlog"=>"service_monitor_errorlog.current",
"wmt_errorlog"=>"wmt_errorlog.current",
"syslog"=>"syslog.txt"
}
Can anyone help? I’ve tried all sorts of commands to merge the 2 arrays into a hash but none of them seem to work
Cheers
Mike
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
EDIT
I've just found out hashs have to have unique keys so could anyone help me work out a way to combine the arrays to form a hash with the values as arrays e.g.
{
"service_monitor_errorlog"=>["service_monitor_errorlog.0", "service_monitor_errorlog.current"]
"wmt_errorlog"=> ["wmt_errorlog.0", "wmt_errorlog.current"]
"syslog"=> ["syslog.txt"]
}

In 1.9 I'd probably do this:
listkey.zip(listvalue).each_with_object(Hash.new{|h,k| h[k] = []}) do |(k,v), h|
h[k] << v
end
Example:
a=['a','b','c','a']
b=[1,2,3,4]
a.zip(b).each_with_object(Hash.new{|h,k| h[k]=[]}) { |(k,v), h| h[k] << v }
#=> {"a"=>[1, 4], "b"=>[2], "c"=>[3]}

For your updated question, an (ugly) solution is
the_hash = listkey.zip(listvalue).inject({}) do | a, (k, v) |
a[k] ||= []
a[k] << v
a
end
or (without the inject)
the_hash = {}
listkey.zip(listvalue).each do | k, v |
the_hash[k] ||= []
the_hash[k] << v
end

Answering the answer after the edit. group_by is a bit inconvenient in this case, so let's use facets' map_by, which is a group_by that allows you to decide what you want to accumulate:
require 'facets'
Hash[xs.zip(ys).map_by { |k, v| [k, v] }]
#=> {"syslog"=>["syslog.txt"],
# "service_monitor_errorlog"=>
# ["service_monitor_errorlog.0", "service_monitor_errorlog.current"],
# "wmt_errorlog"=>["wmt_errorlog.0", "wmt_errorlog.current"]}

Please check this code
a=['a','b','c','a']
b=[1,2,3,4]
c=Hash.new
a.each_with_index do |value,key|
#puts key
#puts value
c[value]=b[key]
end
puts c
Output is
{"a"=>4, "b"=>2, "c"=>3}
This means key should be unique

Related

Understanding Ruby Code

I need some help understanding the below ruby code.
counted = Hash.new(0)
parsed_reponse["result"]["data"].each { |h| counted[h["version"]] += 1 }
counted = Hash[counted.map {|k,v| [k,v.to_s] }]
I understand line 1 creats a hash which I believe is similar to a
dictionary in python.
Line 2 loops through my json data set a adds a
new key and value pair if none exits and increments the count if 1
does exist.
What does the 3rd line do?
Your last line just converts all values to String:
Hash[{a: 2, b: 3}.map {|k, v| [k, v.to_s]}]
#=> {:a=>"2", :b=>"3"}
I would refactor it to:
counted.transform_values!(&:to_s) # for ruby >= 2.4
#=> {:a=>"1", :b=>"2"}
Or for older versions:
counted.each { |k, v| counted[k] = v.to_s }
Because:
counted.map {|k,v| [k,v.to_s] } - creates new array of arrays
Hash[result] - creates new Hash object from result array.
Both steps are redundant, you can just modify existing hash.

Ruby - Initialize has key-value in a loop

I have a hash of key value pairs, similar to -
myhash={'test1' => 'test1', 'test2 => 'test2', ...}
how can I initialize such a hash in a loop? Basically I need it to go from 1..50 with the same test$i values but I cannot figure out how to initialize it properly in a loop instead of doing it manually.
I know how to loop through each key-value pair individually:
myhash.each_pair do |key, value|
but that doesn't help with init
How about:
hash = (1..50).each.with_object({}) do |i, h|
h["test#{i}"] = "test#{i}"
end
If you want to do this lazily, you could do something like below:
hash = Hash.new { |hash, key| key =~ /^test\d+/ ? hash[key] = key : nil}
p hash["test10"]
#=> "test10"
p hash
#=> {"test10"=>"test10"}
The block passed to Hash constructor will be invoked whenever a key is not found in hash, we check whether key follows a certain pattern (based on your need), and create a key-value pair in hash where value is equal to key passed.
(1..50).map { |i| ["test#{i}"] * 2 }.to_h
The solution above is more DRY than two other answers, since "test" is not repeated twice :)
It is BTW, approx 10% faster (that would not be a case when keys and values differ):
require 'benchmark'
n = 500000
Benchmark.bm do |x|
x.report { n.times do ; (1..50).map { |i| ["test#{i}"] * 2 }.to_h ; end }
x.report { n.times do ; (1..50).each.with_object({}) do |i, h| ; h["test#{i}"] = "test#{i}" ; end ; end }
end
user system total real
17.630000 0.000000 17.630000 ( 17.631221)
19.380000 0.000000 19.380000 ( 19.372783)
Or one might use eval:
hash = {}
(1..50).map { |i| eval "hash['test#{i}'] = 'test#{i}'" }
or even JSON#parse:
require 'json'
JSON.parse("{" << (1..50).map { |i| %Q|"test#{i}": "test#{i}"| }.join(',') << "}")
First of all, there's Array#to_h, which converts an array of key-value pairs into a hash.
Second, you can just initialize such a hash in a loop, just do something like this:
target = {}
1.upto(50) do |i|
target["test_#{i}"] = "test_#{i}"
end
You can also do this:
hash = Hash.new{|h, k| h[k] = k.itself}
(1..50).each{|i| hash["test#{i}"]}
hash # => ...

Put every Hash Element inside of an Array Ruby

Let's say I have a Hash like this:
my_hash = {"a"=>{"a1"=>"b1"}, "b"=>"b", "c"=>{"c1"=>{"c2"=>"c3"}}}
And I want to convert every element inside the hash that is also a hash to be placed inside of an Array.
For example, I want the finished Hash to look like this:
{"a"=>[{"a1"=>"b1"}], "b"=>"b", "c"=>[{"c1"=>[{"c2"=>"c3"}]}]}
Here is what I've tried so far, but I need it to work recursively and I'm not quite sure how to make that work:
my_hash.each do |k,v|
if v.class == Hash
my_hash[k] = [] << v
end
end
=> {"a"=>[{"a1"=>"b1"}], "b"=>"b", "c"=>[{"c1"=>{"c2"=>"c3"}}]}
You need to wrap your code into a method and call it recursively.
my_hash = {"a"=>{"a1"=>"b1"}, "b"=>"b", "c"=>{"c1"=>{"c2"=>"c3"}}}
def process(hash)
hash.each do |k,v|
if v.class == Hash
hash[k] = [] << process(v)
end
end
end
p process(my_hash)
#=> {"a"=>[{"a1"=>"b1"}], "b"=>"b", "c"=>[{"c1"=>[{"c2"=>"c3"}]}]}
Recurring proc is another way around:
h = {"a"=>{"a1"=>"b1"}, "b"=>"b", "c"=>{"c1"=>{"c2"=>"c3"}}}
h.map(&(p = proc{|k,v| {k => v.is_a?(Hash) ? [p[*v]] : v}}))
.reduce({}, &:merge)
# => {"a"=>[{"a1"=>"b1"}], "b"=>"b", "c"=>[{"c1"=>[{"c2"=>"c3"}]}]}
It can be done with single reduce, but that way things get even more obfuscated.

How to sort an array of hashes into hashes with multiple values for a key?

I am working on Ruby on Rails project using rails4.
Scenario:
I have an array of hashes. An array contains hashes where keys are the same.
a = [{132=>[1000.0]}, {132=>[4000.0]}, {175=>[1000.0]}, {175=>[1000.0]}, {133=>[1200.0]}]
h = a.each {|key,value| key.each {|k| value}}
#{132=>[1000.0]}
#{132=>[4000.0]}
#{175=>[1000.0]}
#{175=>[1000.0]}
#{133=>[1200.0]}
Problem:
How to get rid off duplicate keys but with values added to unique keys like this:
{132=>[1000,4000]}
{175=>[1000,1000]}
{133=>[1200]}
Thank you.
That would do it:
a.inject({}) {|sum, hash| sum.merge(hash) {|_, old, new| old + new }}
This works for me:
p a.each_with_object(Hash.new([])) { |e, h| e.each { |k, v| h[k] += v } }
# => {132=>[1000.0, 4000.0], 175=>[1000.0, 1000.0], 133=>[1200.0]}
Another way:
a.each_with_object({}) do |g,h|
k, v = g.to_a.flatten
(h[k] ||= []) << v
end
#=> {132=>[1000.0, 4000.0], 175=>[1000.0, 1000.0], 133=>[1200.0]}
or
a.each_with_object(Hash.new { |h,k| h[k]=[] }) do |g,h|
k, v = g.to_a.flatten
h[k] << v
end
#=> {132=>[1000.0, 4000.0], 175=>[1000.0, 1000.0], 133=>[1200.0]}

Select Hash Value when Key is an Array

I have a hash that has some keys as an array like so:
foo = {[45, 121]=>:some_field}
How can I select :some_field where a foo key contains 45?
And secondary to that, if it finds a match, how do I retrieve the other elements in the same key?
Although you can do this, it kind of defeats the purpose of using a hash since you will have to do a linear scan through the entire thing. It would be a lot better to have multiple hash keys for the same value since you can use the hash as an index then.
Example:
found = foo.find { |k, v| k.include?(n) }
found and found[1]
Keep in mind the performance of this will be terrible if you have large numbers of entries in the key and a large number of items in the hash since it will have to test against all keys and all values individually.
foo = {[45, 121]=>:some_field}
foo.detect{ |k,v| k.include? 45 }
#=> [[45, 121], :some_field]
foo.detect{ |k,v| k.include? 45 }.last
#=> :some_field
I would suggest to reverse your hash if it's not one element only:
foo = {[45, 121]=>:some_field, [1, 45, 7] => :some_other_field}
bar = {}
foo.each do |k, v|
k.each do |x|
if bar.has_key?(x)
bar[x] << [[k, v]]
else
bar[x] = [[k, v]]
end
end
end
p bar[45]

Resources