opensoul.org

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:

  • Item 1
  • Item 2
  • Item 3
  • Item 4
  • Item 5
  • Item 6
  • Item 7
  • Item 8

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

array, core_ext, and ruby June 19, 2008

7 Comments

  1. Aaron Pfeifer Aaron Pfeifer June 19, 2008

    Something similar was actually talked about here: http://blog.jayfields.com/2007/09/ruby-arraychunk.html

  2. Nolan Eakins Nolan Eakins June 19, 2008

    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.

  3. Sam Flowers, jr Sam Flowers, jr July 3, 2008

    Very well done code..
    Thanks

  4. Andrew Vit Andrew Vit July 10, 2008

    (“A”..“Z”).to_a.in_groups_of(3).transpose

  5. Brandon Brandon July 11, 2008

    Andrew,

    Except that I want them group as A-I, J-R, and S-Z, not every 3rd letter.

  6. Bill Horsman Bill Horsman July 13, 2008

    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)
    [[“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”, nil]]

  7. Brandon Brandon July 13, 2008

    Bill,

    Not quite. I want 3 arrays total, not n number of 3 element arrays.

    &gt;&gt; ("a".."z").to_a / 3
    =&gt;
    [["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"]]
    

Post a Comment

Comments use textile. Anonymous comments will be deleted.

My name is Brandon Keepers. I like to build things, usually in Ruby or JavaScript. I work at GitHub and live in Holland, MI.

Popular Posts