I need a method that will take a hash and return a hash whose keys are from the old hash and values are the size of the arrays in the old hash. I.e.,
{ 1 => [1,1,1], 2 => [3,4,5], 7 => [9,12] }
# return
{ 1 => 3, 2 => 3, 7 => 2 }
Is there any way to implement this?
One way:
h = { 1 => [1,1,1], 2 => [3,4,5], 7 => [9,12] }
h.merge(h) { |*_,a| a.size }
#=> { 1 => 3, 2 => 3, 7 => 2 }
You can use map to build a new array of [key, value] pairs and then convert it back to a hash using to_h:
input = { 1 => [1,1,1], 2 => [3,4,5], 7 => [9,12] }
input.map { |key, value| [key, value.length] }.to_h
# => {1=>3, 2=>3, 7=>2}
This is quite straightforward: you can use inject to process all the items one by one and compose the result.
input = { 1 => [1,1,1], 2 => [3,4,5], 7 => [9,12] }
input.inject({}) do |result, (key, value)|
result.merge(key => value.size)
end
# => {1=>3, 2=>3, 7=>2}
Even without inject, just use .each to loop all the items and construct the result using a temporary support Hash.
Just for the sake of completeness, a solution using each_with_object:
input = { 1 => [1,1,1], 2 => [3,4,5], 7 => [9,12] }
input.each_with_object({}) { |(k, vs), h| h[k] = vs.size }
#=> {1=>3, 2=>3, 7=>2}
One way is to insert the expected keys and values into the new Hash:
h = { 1 => [1,1,1], 2 => [3,4,5], 7 => [9,12] }
h2 = {}
h.each {|k, v| h2[k] = v.length}
h2
# => {1=>3, 2=>3, 7=>2}
h.keys.zip(h.values.map &:size).to_h
Or you can try this,
h = { 1 => [1,1,1], 2 => [3,4,5], 7 => [9,12] }
Hash[h.map {|key, value| [key, value.length]}]
# => {1=>3, 2=>3, 7=>2}
Related
I have a hash of the following form:
{ 1 => [], 2 => ["A", "B"], 3 => ["C"], 4 => ["B", "C"], 5 => ["D"] }
what's the best way to transform this in Ruby to:
{ "A" => [2], "B" => [2, 4], "C" => [3, 4], "D" => [5], "default" => [1] }
The best way I know.
hash = { 1 => [], 2 => ['A','B'], 3 => ['C'], 4 => ['B','C'], 5 => ['D'] }
new_hash = hash.inject({}) do |result, (key, values)|
values.each do |value|
result[value] ||= []
result[value].push(key)
end
result[:default] = [key] if values.empty?
result
end
puts new_hash
A bit of more functional approach:
array
.flat_map {|k,v| v.product([k])}
.group_by(&:first)
.transform_values {|v| v.map(&:last) }
input = { 1 => [], 2 => ['A', 'B'], 3 => ['C'], 4 => ['B', 'C'], 5 => ['D'], 6 => [] }
output =
input.each_with_object(Hash.new([])) do |(key, values), hash|
values.each { |value| hash[value] += [key] }
hash['default'] += [key] if values.empty?
end
output
# => {"default"=>[1, 6], "A"=>[2], "B"=>[2, 4], "C"=>[3, 4], "D"=>[5]}
I have a hash that maps integers to arrays. For example
{1 => ["abc"], 2 => ["ccc", "ddd"]}
How do I get all the keys from my hash that have arrays with at least 2 elements in them?
{1 => ["abc"], 2 => ["ccc", "ddd"]}.select{|_, a| a.length > 1}.keys
# => [2]
Anything like this?
hash.each_key.select { |key| hash[key].count >= 2 }
One more possible solution :)
{1 => ["abc"], 2 => ["ccc", "ddd"]}.map { |k, v| k if v.size > 1 }.compact
# => [2]
Is there a way I can pick a value in hash of array, and reformat it to be only hash?
Is there any method I can do with it?
Example
[
{
"qset_id" => 1,
"name" => "New1"
},
{
"qset_id" => 2,
"name" => "New2"
}
]
Result
{
1 => {
"name" => "New1"
},
2 => {
"name" => "New2"
}
}
You can basically do arbitary manipulation using reduce function on array or hashes, for example this will get your result
array.reduce({}) do |result, item|
result[item["qset_id"]] = { "name" => item["name"] }
result
end
You can do the same thing with each.with_object do:
array.each.with_object({}) do |item, result|
result[item["qset_id"]] = { "name" => item["name"] }
end
it's basically the same thing but you don't have to make each iteration return the result (called a 'memo object').
You could iterate over the first hash and map it into a second hash:
h1.map{|h| {h['qset_id'] => {'name' => h['name']}} }
# => [{1=>{"name"=>"New1"}}, {2=>{"name"=>"New2"}}]
... but that would return an array. You could pull the elements into a second hash like this:
h2 = {}
h1.each do |h|
h2[h['qset_id']] = {'name' => h['name']}
end
>> h2
=> {1=>{"name"=>"New1"}, 2=>{"name"=>"New2"}}
This is the array that is going to be processed:
[{:name => "blake"}, {:name => "blake"}, {:name => "ashley"}]
and I would like the result to be like this:
[{:name => "blake", :count => 2}, {:name => "ashley", :count => 1}]
I created a new hash called "count" then i used .each to iterate through the array and then count[:count] += 1, but it's not giving me what I'm looking for.
It can be done with built-in library calls like this:
a = [{:name => "blake"}, {:name => "blake"}, {:name => "ashley"}]
a.group_by(&:itself) # group all of the identical elements together
.map{|k, v| k.merge(count: v.length)} # add the "count" element to the hash
Explanation
.group_by(&:itself) will take all the identical components and put them in a hash together under the same key:
>> h = a.group_by(&:itself)
=> {
{:name=>"blake"} => [{:name=>"blake"}, {:name=>"blake"}],
{:name=>"ashley"} => [{:name=>"ashley"}]
}
Notice how the first entry in the hash has an array of two identical elements, and the second entry has an array of one element. To create counts of those, we can use the .length method on the arrays in the hash's values:
>> k, v = h.first
>> v
=> [{:name=>"blake"}, {:name=>"blake"}]
>> v.length
=> 2
Then we can use .map to apply that code to every element in the hash:
>> h.map{|k, v| [k, v.length]}.to_h
=> {
{:name=>"blake"}=>2,
{:name=>"ashley"}=>1
}
Finally, .merge will take two hashes and piece them together into a single hash:
>> {:name=>"blake"}.merge({:count => 1})
=> {:name=>"blake", :count=>1}
Here the new Enumerable#tally introduced with Ruby 2.7.0 comes in hand:
h = [{:name => "blake"}, {:name => "blake"}, {:name => "ashley"}]
h.tally.map {|k, v| k.merge({count: v}) }
#=> [{:name=>"blake", :count=>2}, {:name=>"ashley", :count=>1}]
There are three main ways of doing this.
Use Enumerable#group_by
That is what #user12341234 has done in his or her answer.
Use a counting hash
If arr is your array,
arr.each_with_object(Hash.new(0)) { |g,h| h[g[:name]] += 1 }.
map { |name, count| { :name=>name, :count=>count } }
#=> [{:name=>"blake", :count=>2}, {:name=>"ashley", :count=>1}]
See Hash::new for details.
Use 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.
arr.each_with_object({}) { |g,h| h.update(g[:name]=>1) { |_,o,n| o+n } }.
map { |name, count| { :name=>name, :count=>count } }
#=> [{:name=>"blake", :count=>2}, {:name=>"ashley", :count=>1}]
See the doc Hash#update for details.
Notice that last line is the same for #2 and #3.
I cannot say that one of these approaches is preferable to the others.
From the following array of hashes:
x = [
{"creationDate"=>123456,"createdBy"=>"test_user1"},
{"creationDate"=>123459,"createdBy"=>"test_user1"},
{"creationDate"=>123458,"createdBy"=>"test_user1"},
{"creationDate"=>123454,"createdBy"=>"test_user2"},
{"creationDate"=>123452,"createdBy"=>"test_user2"},
{"creationDate"=>123451,"createdBy"=>"test_user2"}
]
I am trying to find the maximum :creationDate value where :createdBy value is "test_user1". I did this:
x.map {|a| a['creationDate'] if a['createdBy'] == 'test_user1'}
# => [123456,123459,123458,nil,nil,nil]
I want to get rid of the nil so that I can apply max to that array. How do I modify the code above?
What you want to do here is:
x.select { |record| record[:createdBy] == 'test_user1' }.map { |record| record[:creationDate] }.max
# => 123459
In general, to remove nils from an array, you can simply call Array#compact:
[1, nil, nil, 2, 'foo', nil].compact # => [1, 2, "foo"]
It' close to what you plan to do in python:
x.select { |n| n[:createdBy] == "test_user1" }.max_by { |n| n[:creationDate] }
First operation select on records created by "test_user1", while second operation get the maximum of the resulting array based on the creationDate
I'd guess something like :
x.delete_if {|x| x == nil}
This is a good candidate for a map-reduce function such as inject.
x = [{"creationDate" => 123456,"createdBy" => "test_user1"},
{"creationDate" => 123459,"createdBy" => "test_user1"},
{"creationDate" => 123458,"createdBy" => "test_user1"},
{"creationDate" => 123454,"createdBy" => "test_user2"},
{"creationDate" => 123452,"createdBy" => "test_user2"},
{"creationDate" => 123451,"createdBy" => "test_user2"}]
x.inject(nil) do |result, item|
if item["createdBy"] == "test_user1" && (result.nil? or item["creationDate"] > result)
item["creationDate"]
else
result
end
end
Here's a possible implementation of the inject. The block will iterate each item in the collection and will always maintain the highest value.
x.min_by{|h| [h[:createdBy], -h[:creationDate]]}[:creationDate]
# => 123459
[123456, 123459, 123458, nil, nil, nil].compact
# => [123456, 123459, 123458]
This works:
x.map {|a| a['creationDate'] if a['createdBy'] == 'test_user1'}.compact.max
# => 123459