Splitting Hairs and Arrays
Am I just dumb, or is it really a lot harder than it should be to break an array up into a set number of chunks?
For example, I have a list of 8 items that I want to break into 3 arrays, each displayed in their own unordered list, like this:
Brian and I spent a ridiculous amount of time (20 minutes, at least) trying to come up with a clean solution to this seemingly simple problem. The closest thing there is to a solution is Enumerable#each_slice in Ruby core or Array#in_groups_of in Active Support.
<% my_array.each_slice((my_array.size.to_f / 3).ceil) do |list| %>
<ul>
<% list.each do |item| %>
<li><%= item %></li>
<% end %>
</ul>
<% end %>
or
<% my_array.in_groups_of((my_array.size.to_f / 3).ceil, false) do |list| %>
<ul>
<% list.each do |item| %>
<li><%= item %></li>
<% end %>
</ul>
<% end %>
There’s not really a difference between either solution. Both requires that we calculate how many items we want in each list. (We convert the size to a float, divide by the number of columns, then round up. This gives us the same number of items in each column, with the last column having fewer.)
Our solution
We didn’t like having that much logic in the view, so we added a method to enumerable; we thought the division (/) method seemed appropriate since we’re dividing the array into equal parts.
module Enumerable
# Divide into groups
def /(num)
returning [] do |result|
each_slice((size.to_f / num).ceil) {|a| result << a }
end
end
end
Note: this method is now in our awesomeness plugin.
So now we can just divide our array into chunks in the view.
<% (my_array / 3).each do |list| %>
<ul>
<% list.each do |item| %>
<li><%= item %></li>
<% end %>
</ul>
<% end %>
Are we dumb? Is there already a way to do this that wasn’t obvious to us and we just wasted our time (and I wasted even more time blogging about it)?
Update: Thanks to Aaron Pfeifer for pointing out the discussion on Jay Field’s blog about something similar. I’ve refactored this code in awesomeness to be more “robust’ (read: convoluted).







7 comments
Something similar was actually talked about here: http://blog.jayfields.com/2007/09/ruby-arraychunk.html
June 19, 2008 at 08:53 PM
Facets has something like this. Uncovered this with Eli back when we reworking some Rake tasks: http://facets.rubyforge.org/doc/api/core/index.html
That was way after I created a Multiterator to do the same thing. IT went something like: Multiterator(array, 3).each { |a, b, c| puts a + b + c }
+1 for Ruby 1.9 having something that does this.
June 19, 2008 at 11:19 PM
Very well done code.. Thanks
July 03, 2008 at 12:44 PM
(“A”..”Z”).to_a.in_groups_of(3).transpose
July 10, 2008 at 07:57 PM
Andrew,
Except that I want them group as A-I, J-R, and S-Z, not every 3rd letter.
July 11, 2008 at 08:09 AM
Brandon: Andrew’s is almost right, isn’t he? Just skip the transpose (which I didn’t know about incidentally) and it works…
(“A”..”Z”).to_a.in_groups_of(3) [“D”, “E”, “F”], [“G”, “H”, “I”], [“J”, “K”, “L”], [“M”, “N”, “O”], [“P”, “Q”, “R”], [“S”, “T”, “U”], [“V”, “W”, “X”], [“Y”, “Z”, nil]]
July 13, 2008 at 03:31 AM
Bill,
Not quite. I want 3 arrays total, not n number of 3 element arrays.
>> ("a".."z").to_a / 3 => [["a", "b", "c", "d", "e", "f", "g", "h", "i"], ["j", "k", "l", "m", "n", "o", "p", "q", "r"], ["s", "t", "u", "v", "w", "x", "y", "z"]]July 13, 2008 at 08:23 AM
Speak your mind: