Rails - Enumerable Group_By multiple associations - ruby-on-rails

I want to group a collection of objects by their has many relations... like this
s.inventoryitems.group_by{|i| i.locations}
For the sake of simplicity this returns me something like this:
{[1, 2, 3]=>["a"], [2]=>["b", "c"], []=>["d"]}
I'm looking for a result like this though:
{[1] => ["a"], [2] => ["a","b","c"], [3] => ["a"], [] => ["d"]}
I am working on restructuring things so this can all be done in a more intuitive DB & model association oriented way, but in the meantime I need implement this immediately and need to wrangle it with some Ruby and am not sure. Thanks for any help!

You need to expand this, invert it, and re-group it if you want to flip the structure like that. You could do this simply by iterating over it and regrouping manually:
h = { [ 1, 2, 3 ] => [ "a" ], [ 2 ] => [ "b", "c" ], [ ] => [ "d" ] }
s = { }
h.each do |keys, values|
keys.each do |key|
values.each do |value|
s[[ key ]] ||= [ ]
s[[ key ]] << value
end
end
if (keys.empty?)
s[[ ]] = values
end
end
puts s.inspect
# => {[1]=>["a"], [2]=>["a", "b", "c"], [3]=>["a"], []=>["d"]}

Related

Iterate a hash of nested arrays/hashes/strings and perform some logic on them

I want to loop through a hash that contains a mix of nested object types. E.g.
{
"a" => "1",
"b" => ["1", "2"],
"c" => {"d" => "1", "e" => {"f" => ["1", "2", {"g" => ["x", "y", "1"]}]}}
}
and transform all string values to integers, given that they consist of numbers only. So they should match /^\d+$/.
But the important part is the iteration part.
How do I find all the values of key/value pairs as well as all values inside arrays, so I can manipulate them?
It's a bit similar to the deep_symbolize_keys method, only here I'm interested in everything else that hash keys.
My own implementation goes like this, but it's long and I may not have catched all edge cases:
def all_to_i(x)
if x.is_a?(Hash)
x.each do |k, v|
if v.is_a?(Hash)
all_to_i(v)
elsif v.is_a?(Array)
x[k] = v.map { |v2| all_to_i(v2) }
elsif v.is_a?(String)
if v.scan(/^\d+$/) != []
x[k] = v.to_i
else
x[k] = v
end
end
end
elsif x.is_a?(Array)
x.map { |x2| all_to_i(x2) }
elsif x.is_a?(String)
if x.scan(/^\d+$/) != []
x.to_i
else
x
end
end
end
# => {"a"=>1, "b"=>[1, 2], "c"=>{"d"=>1, "e"=>{"f"=>[1, 2, {"g"=>["x", "y", 1]}]}}}

Looking for a rspec matcher that checks a value to be included in array

How can I write this:
Given an array b = ['one', 'two', 'three']
I expect value a to be
included in the array b.
I want to use it together with an all matcher so my final code would look like this:
b = ['one', 'two', 'three']
my_list = [ {'type' => 'one'}, {'type' => 'two'}, {'type' => 'three'} ]
expect(my_list).to all(include("type" => a_value_included_in(b))
Which is testing for:
all hashes from my_list must have a type key whose value is in array b.
Is there such built-in matcher in Rspec?
And how do you check inclusion of a value in the array besides using the obvious reverse: expect([1, 2, 3]).to include(value), which in my example, is not really fitting in?
If you want to check if every string from b is present in any of the hashes from my_list:
b = ['one', 'two', 'three']
my_list = [ {'type' => 'one'}, {'type' => 'two'}, {'type' => 'three'} ]
b.each do |str|
expect(my_list.find { |h| h['type'] == str }).to be_present
end
Or, you can go with:
expect(my_list.map { |h| h['type'] }.sort) to eq(b.sort)
So when someone adds my_list << { 'type' => 'not_in_list' } the spec
will fail.
expect(my_list.map { |h| b.include?(h['type']) }.uniq).to eq(true)
In your case I would simply use (order of elements doesn't matter):
expect(my_list).to match_array(b.map{|v| { 'type' => v })
or use include matcher instead of match_array if b can ba accepted as a subset of my_list.
I don't think that special matcher for such cases exists and/or it's really needed. Still, you can create a custom one if you really can't live w/o it.

Rails group by first column and then second column

I have a Team model that has two attributes, .type and .sub type.
I would like to organize them in a call by team.type first and then team.subtype.
I tried
Team.all.group_by{|e| [e.type, e.sub_type]}
But that gets me
{
["Sport", "Football"] => [
teamObject,
teamObject
],
["Sport", "Soccer"] => [
teamObject,
teamObject
],
}
What I want is....
{
"Sport" => {
"Football" => [
teamObject,
teamObject
],
"Soccer" => [
teamObject,
teamObject
],
},
"Drama" => {
"Band" => [ teamObject, teamObject],
"Dance" => [ teamObject, teamObject],
},
}
What should my query or filter look like? Ideally, I would like to turn this into a scope.
Note: I do not want to create more models, tables, or relationships.
I would try something like this:
Team.all.
group_by { |e| e.type }.
map { |k, v| [k, v.group_by { |e| e.sub_type }].to_h }
Or:
Hash.new { |h, k| h[k] = Hash.new { |h, k| h[k] = [] } }.tap do |hash|
Team.all.each { |t| hash[t.type][t.sub_type] << t }
end

mongoid group_by return a hashmap instead of array of hashes

I want to retrieve a hashmap from a mongoid group_by instead of a array
Product.group_by {|p| p.user_id }
returns an array of mappings
result = Product.group_by {|p| p.user_id }
=> [ {"12354asdf" => [product1, product2, product3]},
{"safakjgh314" => [product4, product5, product6]} ]
I'm currently running the result of this query over the following to achieve a single hash of mappings
result.reduce Hash.new, :merge
=> {"12354asdf" => [product1, product2, product3],
"safakjgh314" => [product4, product5, product6]}
is there a more efficient way to do this?
edit***
After grouping I'd rather operate over the collection with an enumerable that makes sense.
result.each do |k v| k v end
rather than
result.each do |h| h.keys.first, h.values.first end
example of what it currently returns.
[
{user_object => [item1, item2, item3] },
{user2_object => [item1, item2, item3] },
{user2_object => [item1, item2, item3] }
]
Regarding the more compact way to iterate over the array of hashes, you can take each element's first:
result.map(&:first).each do |k, v|
puts k
puts v.count
end
# 12354asdf
# 3
# safakjgh314
# 3

Group Hash by values in ruby

I have a hash in ruby which looks something like this:
{
"admin_milestones"=>"1",
"users_milestones"=>"0",
"admin_goals"=>"1",
"users_goals"=>"0",
"admin_tasks"=>"1",
"users_tasks"=>"0",
"admin_messages"=>"1",
"users_messages"=>"0",
"admin_meetings"=>"1",
"users_meetings"=>"0"
}
I am trying to lookout for a solutions which can cut this hash in to two parts, one with value as 1 and other hash with value as 0.
You can group hash by its value:
h1 = {
"admin_milestones"=>"1",
"users_milestones"=>"0",
"admin_goals"=>"1",
"users_goals"=>"0",
"admin_tasks"=>"1",
"users_tasks"=>"0",
"admin_messages"=>"1",
"users_messages"=>"0",
"admin_meetings"=>"1",
"users_meetings"=>"0"
}
h2 = h1.group_by{|k,v| v}
It will produce a hash grouped by its values like this:
h2 = {"1"=>[["admin_milestones", "1"], ["admin_goals", "1"], ["admin_tasks", "1"], ["admin_messages", "1"], ["admin_meetings", "1"]],
"0"=>[["users_milestones", "0"], ["users_goals", "0"], ["users_tasks", "0"], ["users_messages", "0"], ["users_meetings", "0"]]}
If you want an array as answer the cleanest solution is the partition method.
zeros, ones = my_hash.partition{|key, val| val == '0'}
You should use group_by on the keys arrays and use the value as the grouping element:
h1 = {
"admin_milestones"=>"1",
"users_milestones"=>"0",
"admin_goals"=>"1",
"users_goals"=>"0",
"admin_tasks"=>"1",
"users_tasks"=>"0",
"admin_messages"=>"1",
"users_messages"=>"0",
"admin_meetings"=>"1",
"users_meetings"=>"0"
}
# group_by on the keys, then use the value from the hash as bucket
h2 = h1.keys.group_by { |k| h1[k] }
puts h2.inspect
Returns a hash from value to array of keys:
{
"1" => [
[0] "admin_milestones",
[1] "admin_goals",
[2] "admin_tasks",
[3] "admin_messages",
[4] "admin_meetings"
],
"0" => [
[0] "users_milestones",
[1] "users_goals",
[2] "users_tasks",
[3] "users_messages",
[4] "users_meetings"
]
}
Just Hash.select:
h1.select { |key, value| value == '0' } #=> {"users_milestones"=>"0", "users_goals"=>"0", ...}
h1.select { |key, value| value == '1' } #=> {"admin_milestones"=>"1", "admin_goals"=>"1", ...}
The return value depends on your Ruby version. Ruby 1.8 returns a array of arrays, whereas Ruby 1.9 returns a hash like in the example above.
Similar with https://stackoverflow.com/a/56164608/14718545 you can use group_by but with then, in this case, you will avoid instantiating an extra variable.
{
"admin_milestones" => "1",
"users_milestones" => "0",
"admin_goals" => "1",
"users_goals" => "0",
"admin_tasks" => "1",
"users_tasks" => "0",
"admin_messages" => "1",
"users_messages" => "0",
"admin_meetings" => "1",
"users_meetings" => "0"
}.then { |h| h.keys.group_by { |k| h[k] } }
{"1"=>["admin_milestones", "admin_goals", "admin_tasks", "admin_messages", "admin_meetings"],
"0"=>["users_milestones", "users_goals", "users_tasks", "users_messages", "users_meetings"]}

Resources