Search code examples
arraysrubyenumerable

Split an array into slices, with groupings


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 locations and a non-even number of Objects.


Solution

  • 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.