Reputation: 740
I've got some Ruby code here, that works, but I'm certain I'm not doing it as efficiently as I can.
I have an Array of Objects, along this line:
[
{ name: "foo1", location: "new york" },
{ name: "foo2", location: "new york" },
{ name: "foo3", location: "new york" },
{ name: "bar1", location: "new york" },
{ name: "bar2", location: "new york" },
{ name: "bar3", location: "new york" },
{ name: "baz1", location: "chicago" },
{ name: "baz2", location: "chicago" },
{ name: "baz3", location: "chicago" },
{ name: "quux1", location: "chicago" },
{ name: "quux2", location: "chicago" },
{ name: "quux3", location: "chicago" }
]
I want to create some number of groups - say 3 - where each group contains a semi-equal amount of items, but interspersed by location
.
I tried something like this:
group_size = 3
groups = []
group_size.times do
groups.push([])
end
i = 0
objects.each do |object|
groups[i].push(object)
if i < (group_size - 1)
i += 1
else
i = 0
end
end
This returns a groups
object, that looks like:
[
[{:name=>"foo1", :location=>"new york"},
{:name=>"bar1", :location=>"new york"},
{:name=>"baz1", :location=>"chicago"},
{:name=>"quux1", :location=>"chicago"}],
[{:name=>"foo2", :location=>"new york"},
{:name=>"bar2", :location=>"new york"},
{:name=>"baz2", :location=>"chicago"},
{:name=>"quux2", :location=>"chicago"}],
[{:name=>"foo3", :location=>"new york"},
{:name=>"bar3", :location=>"new york"},
{:name=>"baz3", :location=>"chicago"},
{:name=>"quux3", :location=>"chicago"}]
]
So you can see there's a couple of objects from each location in each grouping.
I played around with each_slice()
and group_by()
, even tried to use inject([])
- but I couldn't figure out a more elegant method to do this.
I'm hoping it's something that I'm overlooking - and I need to account for more location
s and a non-even number of Objects.
Upvotes: 2
Views: 95
Reputation: 29478
a.each_slice(group_size).to_a.transpose
Will work given that your data is accurately portrayed in the example. If it is not please supply accurate data so that we can answer the question more appropriately.
e.g.
a= [
{ name: "foo1", location: "new york" },
{ name: "foo2", location: "new york" },
{ name: "foo3", location: "new york" },
{ name: "bar1", location: "new york" },
{ name: "bar2", location: "new york" },
{ name: "bar3", location: "new york" },
{ name: "baz1", location: "chicago" },
{ name: "baz2", location: "chicago" },
{ name: "baz3", location: "chicago" },
{ name: "quux1", location: "chicago" },
{ name: "quux2", location: "chicago" },
{ name: "quux3", location: "chicago" }
]
group_size = 3
a.each_slice(group_size).to_a.transpose
#=> [
[
{:name=>"foo1", :location=>"new york"},
{:name=>"bar1", :location=>"new york"},
{:name=>"baz1", :location=>"chicago"},
{:name=>"quux1", :location=>"chicago"}
],
[
{:name=>"foo2", :location=>"new york"},
{:name=>"bar2", :location=>"new york"},
{:name=>"baz2", :location=>"chicago"},
{:name=>"quux2", :location=>"chicago"}
],
[
{:name=>"foo3", :location=>"new york"},
{:name=>"bar3", :location=>"new york"},
{:name=>"baz3", :location=>"chicago"},
{:name=>"quux3", :location=>"chicago"}
]
]
each_slice
3 will turn this into 4 equal groups (numbered 1,2,3) in your example. transpose
will then turn these 4 groups into 3 groups of 4.
If the locations are not necessarily in order you can add sorting to the front of the method chain
a.sort_by { |h| h[:location] }.each_slice(group_size).to_a.transpose
Update
It was pointed out that an uneven number of arguments for transpose
will raise. My first though was to go with @CarySwoveland's approach but since he already posted it I came up with something a little different
class Array
def indifferent_transpose
arr = self.map(&:dup)
max = arr.map(&:size).max
arr.each {|a| a.push(*([nil] * (max - a.size)))}
arr.transpose.map(&:compact)
end
end
then you can still use the same methodology
a << {name: "foobar1", location: "taiwan" }
a.each_slice(group_size).to_a.indifferent_transpose
#=> [[{:name=>"foo1", :location=>"new york"},
{:name=>"bar1", :location=>"new york"},
{:name=>"baz1", :location=>"chicago"},
{:name=>"quux1", :location=>"chicago"},
#note the extras values will be placed in the group arrays in order
{:name=>"foobar4", :location=>"taiwan"}],
[{:name=>"foo2", :location=>"new york"},
{:name=>"bar2", :location=>"new york"},
{:name=>"baz2", :location=>"chicago"},
{:name=>"quux2", :location=>"chicago"}],
[{:name=>"foo3", :location=>"new york"},
{:name=>"bar3", :location=>"new york"},
{:name=>"baz3", :location=>"chicago"},
{:name=>"quux3", :location=>"chicago"}]]
Upvotes: 3
Reputation: 110725
Here's another way to do it.
Code
def group_em(a, ngroups)
a.each_with_index.with_object(Array.new(ngroups) {[]}) {|(e,i),arr|
arr[i%ngroups] << e}
end
Example
a = [
{ name: "foo1", location: "new york" },
{ name: "foo2", location: "new york" },
{ name: "foo3", location: "new york" },
{ name: "bar1", location: "new york" },
{ name: "bar2", location: "new york" },
{ name: "bar3", location: "new york" },
{ name: "baz1", location: "chicago" },
{ name: "baz2", location: "chicago" },
{ name: "baz3", location: "chicago" },
{ name: "quux1", location: "chicago" },
{ name: "quux2", location: "chicago" }
]
Note that I've omitted the last element of a
from the question in order for a
to have an odd number of elements.
group_em(a,3)
#=> [[{:name=>"foo1", :location=>"new york"},
# {:name=>"bar1", :location=>"new york"},
# {:name=>"baz1", :location=>"chicago" },
# {:name=>"quux1", :location=>"chicago" }],
# [{:name=>"foo2", :location=>"new york"},
# {:name=>"bar2", :location=>"new york"},
# {:name=>"baz2", :location=>"chicago" },
# {:name=>"quux2", :location=>"chicago" }],
# [{:name=>"foo3", :location=>"new york"},
# {:name=>"bar3", :location=>"new york"},
# {:name=>"baz3", :location=>"chicago" }]]
Upvotes: 2
Reputation: 80075
Yes, this bookkeeping with i
is usually a sign there should be something better. I came up with:
ar =[
{ name: "foo1", location: "new york" },
{ name: "foo2", location: "new york" },
{ name: "foo3", location: "new york" },
{ name: "bar1", location: "new york" },
{ name: "bar2", location: "new york" },
{ name: "bar3", location: "new york" },
{ name: "baz1", location: "chicago" },
{ name: "baz2", location: "chicago" },
{ name: "baz3", location: "chicago" },
{ name: "quux1", location: "chicago" },
{ name: "quux2", location: "chicago" },
{ name: "quux3", location: "chicago" }
]
# next line handles unsorted arrays, irrelevant with this data
ar = ar.sort_by{|h| h[:location]}
num_groups = 3
groups = Array.new(num_groups){[]}
wheel = groups.cycle
ar.each{|h| wheel.next << h}
# done.
p groups
# => [[{:name=>"baz1", :location=>"chicago"}, {:name=>"quux1", :location=>"chicago"}, {:name=>"foo1", :location=>"new york"}, ...]
because I like the cycle method.
Upvotes: 4