I have a trouble for several days and I need help to solve it.
I have an hash with multiple values for the same key :
{"answers":
[
{"id":1,"value":true},
{"id":3,"value":false},
{"id":2,"value":3},
{"id":1,"value":false},
{"id":2,"value":false},
{"id":2,"value":1}
]
}
I want a method to group all the values for one key, as an exemple :
{
"answers": [
{
"id":1, "value": [true, false]
},
{
"id":3, "value": [false]
},
{
"id":2, "value":[3, false, 1]
}
]
}
I've tried with the reduce method, but I cant find a way to link values to keys.
Anyone can help me with that ?
Thanks!
It looks like you want Enumerable#group_by to regroup the array of hashes by the :id key in each hash.
This method takes the answers array and returns a new, transformed answers array:
def transform_answers(answers)
answers
.group_by { |h| h[:id] }
.each_value { |a| a.map! { |h| h[:value] } }
.map { |id, value| { id: id, value: value } }
end
You can use it like this:
hash = {
answers: [
{ id: 1, value: true },
{ id: 1, value: false },
{ id: 2, value: 3 },
{ id: 2, value: false },
{ id: 2, value: 1 },
{ id: 3, value: false }
]
}
transformed_answers = transform_answers(hash[:answers]) # => [{:id=>1, :value=>[true, false]}, {:id=>2, :value=>[3, false, 1]}, {:id=>3, :value=>[false]}]
You can easily take the transformed answers and put them back into a hash resembling the original input:
transformed_hash = { answers: transformed_answers }
hash = {
answers: [
{ id: 1, value: true },
{ id: 1, value: false },
{ id: 2, value: 3 },
{ id: 2, value: false },
{ id: 2, value: 1 },
{ id: 3, value: false }
]
}
def doit(answers)
answers.each_with_object({}) do |g,h|
h.update(g[:id]=>{ id: g[:id], value: [g[:value]] }) do |_,o,n|
{ id: o[:id], value: o[:value]+n[:value] }
end
end.values
end
{ answers: doit(hash[:answers]) }
#=> {:answers=>[
# {:id=>1, :value=>[true, false]},
# {:id=>2, :value=>[3, false, 1]},
# {:id=>3, :value=>[false]}
# ]
# }
This uses the form of Hash#update (aka merge!) that employs a block to determine the values of keys that are present in both hashes being merged. That block is
do |_k,o,n|
{ id: o[:id], value: o[:value]+n[:value] }
end
See the doc for update for definitions of the three block variables, _k, o and n. I've written the first block variable (the common key) _k, rather than k, to signify that it is not used in the block calculation.
Note that before values is executed in doit the method has constructed the following hash.
{1=>{:id=>1, :value=>[true, false]},
2=>{:id=>2, :value=>[3, false, 1]},
3=>{:id=>3, :value=>[false]}}
Related
I have an array of hashes containing strings like this:
array = [
{name: "Gad", color: "blue"},
{name: "Lad", color: "red"},
{name: "Mad", color: "green"},
{name: "Sad", color: "blue"},
{name: "Sad", color: "green"}
]
I tried:
require 'pp'
array = [
{name: "Gad", color: "blue"},
{name: "Lad", color: "red"},
{name: "Mad", color: "green"},
{name: "Sad", color: "blue"},
{name: "Sad", color: "green"}
]
pp array.partition { |x| x[:name] }
but the result is quiet wrong for me:
[[{:name=>"Gad", :color=>"blue"},
{:name=>"Lad", :color=>"red"},
{:name=>"Mad", :color=>"green"},
{:name=>"Sad", :color=>"blue"},
{:name=>"Sad", :color=>"green"}],
[]]
The expected result is following:
[[{:name=>"Gad", :color=>"blue"}],
[{:name=>"Lad", :color=>"red"}],
[{:name=>"Mad", :color=>"green"}],
[{:name=>"Sad", :color=>"blue"}, {:name=>"Sad", :color=>"green"}]]
a.group_by { |hash| hash[:name] }.values
# [
# [{:name=>"Gad", :color=>"blue"}],
# [{:name=>"Lad", :color=>"red"}],
# [{:name=>"Mad", :color=>"green"}],
# [{:name=>"Sad", :color=>"blue"}, {:name=>"Sad", :color=>"green"}]
# ]
References:
Enumerable#group_by
Hash#values
If I may say so, an array of arrays of hashes with the same name and different colors probably isn't the best format for your data.
It contains redudant information and lookup is slow and verbose.
You could build a single hash with name as key and and an array of colors :
array = [
{ name: 'Gad', color: 'blue' },
{ name: 'Lad', color: 'red' },
{ name: 'Mad', color: 'green' },
{ name: 'Sad', color: 'blue' },
{ name: 'Sad', color: 'green' }
]
colors = Hash.new { |h, k| h[k] = [] }
colors = array.each_with_object(colors) do |hash, colors|
colors[hash[:name]] << hash[:color]
end
p colors
#=> {"Gad"=>["blue"], "Lad"=>["red"], "Mad"=>["green"], "Sad"=>["blue", "green"]}
Using group_by might be better suited to this. For example:
array.group_by { |x| x[:name] }.map(&:last)
# => [[{:name=>"Gad", :color=>"blue"}], [{:name=>"Lad", :color=>"red"}], [{:name=>"Mad", :color=>"green"}], [{:name=>"Sad", :color=>"blue"}, {:name=>"Sad", :color=>"green"}]]
This uses the form of Hash.update (aka merge!) that employs a hash to determine the values of keys that are present in both hashes being merged.
array.each_with_object({}) { |g,h| h.update(g[:name]=>[g]) { |_,o,n| o+n } }.values
#=> [[{:name=>"Gad", :color=>"blue"}],
# [{:name=>"Lad", :color=>"red"}],
# [{:name=>"Mad", :color=>"green"}],
# [{:name=>"Sad", :color=>"blue"}, {:name=>"Sad", :color=>"green"}]]
To create the data structure #Eric suggested, this would be:
array.each_with_object({}) { |g,h| h.update(g[:name]=>[g[:color]]) { |_,o,n| o+n } }
#=> {"Gad"=>["blue"], "Lad"=>["red"], "Mad"=>["green"], "Sad"=>["blue", "green"]}
I have a ruby hash which looks like:
{ id: 123, name: "test" }
I would like to convert it to:
{ "id" => 123, "name" => "test" }
If you are using Rails or ActiveSupport:
hash = { id: 123, description: "desc" }
hash.stringify #=> { "id" => 123, "name" => "test" }
If you are not:
hash = { id: 123, name: "test" }
Hash[hash.map { |key, value| [key.to_s, value] }] #=> { "id" => 123, "name" => "test" }
I love each_with_object in this case :
hash = { id: 123, name: "test" }
hash.each_with_object({}) { |(key, value), h| h[key.to_s] = value }
#=> { "id" => 123, "name" => "test" }
In pure Ruby (without Rails), you can do this with a combination of Enumerable#map and Array#to_h:
hash = { id: 123, name: "test" }
hash.map{|key, v| [key.to_s, v] }.to_h
{ id: 123, name: "test" }.transform_keys(&:to_s)
#=> {"id"=>123, "name"=>"test"}
See Hash#transform_keys.
I have an array of hashes like the following:
records = [
{ id: "PU525", note: "Foo" },
{ id: "PU525", note: "Bar" },
{ id: "DT525", note: "Hello World" },
{ id: "PU680", note: "Fubar" }
]
The end result should be:
result = [
{ id: "PU525", note: "FooBar" },
{ id: "DT525", note: "Hello World" },
{ id: "PU680", note: "Fubar" }
]
I started playing a little bit with Enumerable#group_by with the following:
results = records.group_by { |record| record[:id] }
# Results in:
# {
# "PU525": [
# { id: "PU525", note: "Foo" },
# { id: "PU525", note: "Bar" }
# ],
# "DT525": { id: "DT525", note: "Hello World" },
# "PU680": { id: "PU680", note: "Fubar" }
# }
The next step would be to use inject to reduce the data down further, but I'm wondering if there's an easier way to reduce down the original array to what I'm looking for as a result without so many steps?
You are nearly done:
records = [
{ id: "PU525", note: "Foo" },
{ id: "PU525", note: "Bar" },
{ id: "DT525", note: "Hello World" },
{ id: "PU680", note: "Fubar" }
]
arr = records.group_by { |record| record[:id] }
.map do |k, v|
[k, v.reduce('') { |memo, h| memo + h[:note] } ]
end
.map do |a|
{ id: a.first, note: a.last }
end
#⇒ [
# { id: "PU525", note: "FooBar" },
# { id: "DT525", note: "Hello World" },
# { id: "PU680", note: "Fubar" }
# ]
UPD After all, you have me entagled with this silly group_by approach. Everything is just easier.
records.inject({}) do |memo, el|
(memo[el[:id]] ||= '') << el[:note]
memo
end.map { |k, v| { id: k, note: v } }
Here is another way, after using #group_by :
records = [
{ id: "PU525", note: "Foo" },
{ id: "PU525", note: "Bar" },
{ id: "DT525", note: "Hello World" },
{ id: "PU680", note: "Fubar" }
]
hsh = records.group_by { |record| record[:id] }.map do |_, v|
v.inject do |h1, h2|
h1.update(h2) { |k, o, n| k == :note ? o << n : n }
end
end
p hsh
# >> [{:id=>"PU525", :note=>"FooBar"}, {:id=>"DT525", :note=>"Hello World"}, {:id=>"PU680", :note=>"Fubar"}]
Here are a couple of variants of the above.
Using Enumerable#group_by
records.group_by { |h| h[:id] }.map { |id, arr|
{ id: id, note: arr.map { |h| h[:note] }.join } }
#=> [{:id=>"PU525", :note=>"FooBar"},
# {:id=>"DT525", :note=>"Hello World"},
# {:id=>"PU680", :note=>"Fubar"}]
Using Hash#update (aka merge!)
records.each_with_object({}) { |g,h| h.update(g[:id]=>g) { |_,og,ng|
{ id: g[:id], note: og[:note]+ng[:note] } } }.values
#=>[{:id=>"PU525", :note=>"FooBar"},
# {:id=>"DT525", :note=>"Hello World"},
# {:id=>"PU680", :note=>"Fubar"}]
Perhaps I'm missing something obvious. It seems tricky to filter a hash by another hash or multiple key/value pairs.
fruit = [
{ name: "apple", color: "red", pieable: true },
{ name: "orange", color: "orange", pieable: false },
{ name: "grape", color: "purple", pieable: false },
{ name: "cherry", color: "red", pieable: true },
{ name: "banana", color: "yellow", pieable: true },
{ name: "tomato", color: "red", pieable: false }
]
filter = { color: "red", pieable: true }
# some awesome one-liner? would return
[
{ name: "apple", color: "red", pieable: true },
{ name: "cherry", color: "red", pieable: true }
]
The array of hashes I don't think is the problem. I don't even know how to test a hash by another arbitrary hash. I'm using Rails so anything out of active_support etc is fine.
COULD be made into a one liner. But multi-line is cleaner.
fruit.select do |hash| # or use select!
filter.all? do |key, value|
value == hash[key]
end
end
If you allow two lines it could also be made into an efficient "one-liner" like so:
keys, values = filter.to_a.transpose
fruit.select { |f| f.values_at(*keys) == values }
Not the most efficient (you could just use an array form of filter to avoid repeated conversions), but:
fruit.select {|f| (filter.to_a - f.to_a).empty? }
I'd be inclined to use Enumerable#group_by for this:
fruit.group_by { |g| { color: g[:color], pieable: g[:pieable] } }[filter]
#=> [{:name=>"apple", :color=>"red", :pieable=>true},
# {:name=>"cherry", :color=>"red", :pieable=>true}]
Tony Arcieri (#bascule) gave this really nice solution on twitter.
require 'active_support/core_ext' # unneeded if you are in a rails app
fruit.select { |hash| hash.slice(*filter.keys) == filter }
And it works.
# [{:name=>"apple", :color=>"red", :pieable=>true},
# {:name=>"cherry", :color=>"red", :pieable=>true}]
Try this
fruit.select{ |hash| (filter.to_a & hash.to_a) == filter.to_a }
=> [{:name=>"apple", :color=>"red", :pieable=>true},
{:name=>"cherry", :color=>"red", :pieable=>true}]
I'd like to get back an array of hashes based on sport and type combination
I've got the following array:
[
{ sport: "football", type: 11, other_key: 5 },
{ sport: "football", type: 12, othey_key: 100 },
{ sport: "football", type: 11, othey_key: 700 },
{ sport: "basketball", type: 11, othey_key: 200 },
{ sport: "basketball", type: 11, othey_key: 500 }
]
I'd like to get back:
[
{ sport: "football", type: 11, other_key: 5 },
{ sport: "football", type: 12, othey_key: 100 },
{ sport: "basketball", type: 11, othey_key: 200 },
]
I tried to use (pseudocode):
[{}, {}, {}].uniq { |m| m.sport and m.type }
I know I can create such array with loops, I'm quite new to ruby and I'm curious if there's a better (more elegant) way to do it.
Try using Array#values_at to generate an array to uniq by.
sports.uniq{ |s| s.values_at(:sport, :type) }
One solution is to build some sort of key with the sport and type, like so:
arr.uniq{ |m| "#{m[:sport]}-#{m[:type]}" }
The way uniq works is that it uses the return value of the block to compare elements.
require 'pp'
data = [
{ sport: "football", type: 11, other_key: 5 },
{ sport: "football", type: 12, othey_key: 100 },
{ sport: "football", type: 11, othey_key: 700 },
{ sport: "basketball", type: 11, othey_key: 200 },
{ sport: "basketball", type: 11, othey_key: 500 }
]
results = data.uniq do |hash|
[hash[:sport], hash[:type]]
end
pp results
--output:--
[{:sport=>"football", :type=>11, :other_key=>5},
{:sport=>"football", :type=>12, :othey_key=>100},
{:sport=>"basketball", :type=>11, :othey_key=>200}]