Ruby 1.9 - Convert hash based off regex - ruby

Need to convert {"P1" => "ABC", "R1" => "15", "P2" => "LOP", "R2" => "22"}
The hash keys could be in any order.
For instance they could also be {"P1" => "ABC", "R2" => "22", "R1" => "15", "P2" => "LOP"}
or {"R1" => "15", "R2" => "22", "P1" => "ABC", "P2" => "LOP"}
Into this {"ABC" => "15", "LOP" => "22"}
The matcher between keys is the digit.
I'm looking for {P1.value => R1.value, P2.value => R2.value}
P indicating key and R indicating value
The way I'm currently doing it is looping over the initial hash and creating a hash like {1 => 'ABC', 2 => 'LOP'} and also creating {1 => "15", 2 => "22"}.
I then loop over the first new hash and use the key to match the second hash and create the final hash. {"ABC" => "15", "LOP" => "22"}
I'm sure there is a more elegant solution but I just can't think of it.

Check Kernel#Hash. This works for any number of Pn/Rn:
h = {"P1" => "ABC", "R2" => "22", "R1" => "15", "P2" => "LOP"}
Hash[*h.sort_by { |k, v| [k[1..-1].to_i, k[0]] }.map { |k, v| v }]
#=> {"ABC"=>"15", "LOP"=>"22"}
A more orthodox approach without tricks on the order of the keys:
groups = h.group_by { |k, v| k[1..-1].to_i }
Hash[groups.map do |id, pairs|
h2 = Hash[pairs]
h2.values_at("P#{id}", "R#{id}")
end]
#=> {"ABC"=>"15", "LOP"=>"22"}

One more option:
Hash[h.keys.grep(/P\d+/).map {|k| [h[k], h[k.tr('P','R')]] }]

h = {"P1" => "ABC", "R2" => "22", "R1" => "15", "P2" => "LOP"}
Hash[*h.sort_by{|k, _| [k[/\d+/].to_i, k[/\D+/]]}.map(&:last)]

Related

Merge hashes with same key value pair inside array

How to merge hashes within array with same key, value pair? I have following array,
arry = [{"id" => 1, "val1" => 123},
{"id" => 2, "val1" => 234},
{"id" => 1, "val2" => 321},
{"id" => 1, "val3" => 445},
{"id" => 3, "val3" => 334}]
Want to get
arry = [{"id" => 1, "val1" => 123, "val2" => 321, "val3" => 445},
{"id" => 2, "val1" => 234},
{"id" => 3, "val3" => 334}]
is there ruby way to do it? Tried few ways but no success so far.
The arry you have posted is not a valid ruby array in the first place (I fixed it in my edit.)
arry.
group_by { |h| h["id"] }.
values.
map { |a| a.reduce(&:merge) }
#⇒ [{"id"=>1, "val1"=>123, "val2"=>321, "val3"=>445},
# {"id"=>2, "val1"=>234}, {"id"=>3, "val3"=>334}]
If your input might have same keys within the same "id" (like {"id" => 1, "val1" => 123}, {"id" => 1, "val1" => 456},) you are to decide how to merge them. In any case, Hash#merge with a block would be your friend there.
arry.each_with_object({}) { |g,h| h.update(g["id"]=>g) { |_,o,n| o.merge(n) } }.values
#=> [{"id"=>1, "val1"=>123, "val2"=>321, "val3"=>445},
# {"id"=>2, "val1"=>234},
# {"id"=>3, "val3"=>334}]
Note the receiver of values is:
{1=>{"id"=>1, "val1"=>123, "val2"=>321, "val3"=>445},
2=>{"id"=>2, "val1"=>234},
3=>{"id"=>3, "val3"=>334}}
This uses the form of Hash#update (aka merge) that employs the block { |_,o,n| o.merge(n) } to determine the values of keys that are present in both hashes being merged. See the doc for descriptions of the three block variables. (I've used an underscore for the first, the common key, to signify that it is not used in the block calculation.)
This should work too,
arry.group_by { |a| a['id'] }.map{|_, ar| ar.reduce(:merge)}
This would return,
[{"id"=>1, "val1"=>123, "val2"=>321, "val3"=>445}, {"id"=>2, "val1"=>234}, {"id"=>3, "val3"=>334}]

how can i perform a query on a hash, and get result another hash?

I am trying to do a query against a ruby hash, which is much alike this:
{"client1" => {"tag" => "13", "host" => "client1.example.com", ...}, "client2" => {"tag" => "11", ...} }
and I would like to map it to only the client names with their tags, like this:
{"client1" => "13", "client2" => "11"}
I have been struggeling with .each and .select and .find but haven't figured it out yet. I am pretty sure it is not that hard, does anybody know? Thanks
You could do the same as below
data = {
"client1" => {"tag" => "13", "host" => "client1.example.com"},
"client2" => {"tag" => "11"}
}
desired_data = Hash.new
data.each do |k,v|
desired_data[k] = v["tag"]
end
desired_data will contain your result.
As suggested by #sawa you could also use
data.each_with_object({}){|(k, v), h| h[k] = v["tag"]}
Use map:
test_hash = {"client1" => {"tag" => "13", "host" => "client1.example.com"}, "client2" => {"tag" => "11"} }
test_hash.map{|k,v| [k, v['tag']]}.to_h
#=> {"client1"=>"13", "client2"=>"11"}
One way is to merge the hash with itself, using the form of Hash#merge that employs a block to determine the values of keys that are present in both hashes being merged, which in this case is all keys.
h = {"client1" => {"tag" => "13", "host" => "client1.example.com"},
"client2" => {"tag" => "11"} }
h.merge(h) { |*,v| v["tag"] }
#=> {"client1"=>"13", "client2"=>"11"}
As explained in the doc, the block has three variables, often written |key, old_value, new_value|. Here old_value and new_value are the same. The asterisk in |*, new_value| is a placeholder for all but the last block variable.

Create a combination of a array of hash

I have an array like:
hasharray=[{"a" => "b" } , {"c" => "d"} , {"e" => "f"}]
I want to create all combinations of this array hash of length min to max.
For instance, with min=0 and max=2, the code should return:
resultarray=[
{},
{"a" => "b" },
{"c" => "d"},
{"e" => "f"},
{"a" => "b" } , {"c" => "d"},
{"c" => "d"} , {"e" => "f"},
{"a" => "b" },{"e" => "f"}
]
How do I do it?
min = 0
max = 2
min.upto(max).flat_map {|n| hasharray.combination(n).to_a }
# => [
# [],
# [{"a"=>"b"}], [{"c"=>"d"}], [{"e"=>"f"}],
# [{"a"=>"b"}, {"c"=>"d"}], [{"a"=>"b"}, {"e"=>"f"}], [{"c"=>"d"}, {"e"=>"f"}]
# ]

Convert SQL result to hash with ID keys with Ruby

I have the following array (SQL result):
[
{:id => 1, :field1 => "one", :field2 => "two"},
{:id => 2, :field1 => "one", :field2 => "two"},
...
]
What I want is:
{
1 => {:field1 => "one", :field2 => "two"},
2 => {:field1 => "one", :field2 => "two"},
...
}
Now I do like the following:
data = {}
result.each do |row|
data[row[:id]] = {:field1 => row[:field1], :field2 => row[:field2]}
end
I'm absolutely sure that's wrong way. What is the best way to do it with Ruby? Is there are any snippet like map or something else?
Hash[arr.map { |h| [h.delete(:id), h] }]
One line :)
hash = arr.clone.each_with_object({}) { |e,res| res[e.delete(:id)] = e }
clone is for not destroying arr variable
Something like this, maybe?
arr = [
{:id => 1, :field1 => "one", :field2 => "two"},
{:id => 2, :field1 => "one", :field2 => "two"}
]
hash = arr.each_with_object({}) do |el, memo|
id = el.delete(:id)
memo[id] = el
end
hash # => {1=>{:field1=>"one", :field2=>"two"}, 2=>{:field1=>"one", :field2=>"two"}}

Reverse a hash in Ruby

How would I reverse the elements in the hash, keeping the same values and keys, but reversing their order in the hash.
Like so:
{ "4" => "happiness", "10" => "cool", "lala" => "54", "1" => "spider" }
And convert that to:
{ "1" => "spider", "lala" => "54", "10" => "cool", "4" => "happiness" }
Or, perhaps I could run a each loop backwards, starting from the last element in the hash, rather than the first?
You could convert the Hash to an Array, reverse that, and then convert it back to a Hash:
reversed_h = Hash[h.to_a.reverse]
Hash#to_a gives you an array of arrays, the inner arrays are simple [key,value] pairs, then you reverse that array using Array#reverse, and Hash[] converts the [key,value] pairs back into a Hash.
Ruby 2.1 adds an Array#to_h method so you can now say:
reversed_h = h.to_a.reverse.to_h
In Ruby 2.1+ you can combine reverse_each and to_h:
{foo: 1, bar: 2}.reverse_each.to_h
#=> {:bar=>2, :foo=>1}
In pure ruby, you can do it by hash.map(&:reverse).to_h or hash.reverse_each.to_h
In rails, you can do it by hash.invert
hash = { "4" => "happiness", "10" => "cool", "lala" => "54", "1" => "spider" }
reversed_hash = Hash[hash.to_a.reverse]
h = { "4" => "happiness", "10" => "cool", "lala" => "54", "1" => "spider" }
p Hash[h.reverse_each.map{|e| e}]
#=> {"1"=>"spider", "lala"=>"54", "10"=>"cool", "4"=>"happiness"}
But this leaves a bad taste (just like the other answers, which work fine just like this one). If you have to do this, it could be an indication that a Hash was not the best choice.
Alternatively, you can use reduce and merge to add the item to the front of a new hash:
hash = { "4" => "happiness", "10" => "cool", "lala" => "54", "1" => "spider" }
hash.reduce({}){ |memo, object| Hash[*object].merge(memo) }
but, that's crazy :D
reversed_h = Hash[h.to_a.collect(&:reverse)]
In Ruby 1.8.7, the order of elements in a hash is documented to be not under our control, so none of the above methods work. In Ruby 1.9.3, things work and are documented in the way that the other answers rely upon.
$ irb1.8
h = { "4" => "happiness", "10" => "cool", "lala" => "54", "1" => "spider" }
Hash[h.to_a().reverse()]
=> {"lala"=>"54", "1"=>"spider", "10"=>"cool", "4"=>"happiness"}
quit
$ irb1.9.1
h = { "4" => "happiness", "10" => "cool", "lala" => "54", "1" => "spider" }
Hash[h.to_a().reverse()]
=>{"1"=>"spider", "lala"=>"54", "10"=>"cool", "4"=>"happiness"}
The Ruby 1.8.7 way was ingrained so firmly for me that I misunderstood the question for quite some time. I thought it requested a way to Hash#invert: ie to transform the hash such that the range maps to the domain. That method discards duplicates. Luís Ramalho proffers a method that doesn't, but it's a bit clunky. This is a little shorter:
$ irb
def invertWithDuplicates(original)
inverse = Hash.new() { |hash, key| hash[key] = []; }
original.each_pair() { |key, value| inverse[value].push(key); }
return inverse
end
h = { "4" => "happiness", "10" => "cool", "lala" => "54", "1" => "cool" }
invertWithDuplicates(h)
=> {"happiness"=>["4"], "cool"=>["1", "10"], "54"=>["lala"]}
Sorry to drift away from the OP's intended topic, though I submit that this does fit the post's title "Reverse a hash in Ruby".
if need:
hash = {:a => :x, :b => :y, :c => :y, :d => :z}
to:
{:x => [:a], :y => [:b, c], :z => [:d] }
can:
h={};hash.to_a.each{|e|h[e[1]]||=[];h[e[1]]<<e[0]};h

Resources