in_vertical_groups_of

Hi all,

Here's another contribution for anyone interested.

In Railscast episode 28, Ryan Bates showed us in_groups_of which allows you to group array members such as

>> list = [1, 2, 3, 4, 5, 6, 7]
=> [1, 2, 3, 4, 5, 6, 7]
>> list.in_groups_of(3)
=> [[1, 2, 3], [4, 5, 6], [7, nil, nil]]

This allows you to iterate over the groups and, for example, display a list of things in a tabular format.

1 2 3
4 5 6
7

I thought this was pretty cool, but wanted to go vertically down the column first, instead of horizontally

1 4 7
2 5
3 6

Well, last night I decided I really needed this vertical grouping, so ...

module ActiveSupport
  module CoreExtensions
    module Array
      module Grouping
        def in_vertical_groups_of(number, fill_with = nil, &block)
          collection = dup
          per_column = (collection.size.to_f / number).ceil
          
          new_collection = []
          
          (0...per_column).each do |i|
            (0...number).each do |multiplier|
              offset = i + (per_column * multiplier)
              new_collection << (offset < collection.size ? collection[offset] : (fill_with != false ? fill_with : nil))
            end
          end
          
          return new_collection.in_groups_of(number, fill_with, &block)
        end
      end
    end
  end
end

All this really does is reorders the array then calls in_groups_of. The only caveat (that I'm aware of right now), is you have to fill "empty" spots in the array or the column spanning won't work right. So in a case where could do this

>> list.in_groups_of(3, false)
=> [[1, 2, 3], [4, 5, 6], [7]]

in_vertical_groups_of will return

>> list.in_vertical_groups_of(3, false)
=> [[1, 4, 7], [2, 5, nil], [3, 6, nil]]

The ultimate end of this is

@members = Member.find(:all)

...

<table class="wgg_table">
  <% @members.in_vertical_groups_of(3) do |group| %>
  <tr style="font-size: 80%">
    <% group.each do |member| %>
    <%= render :partial => 'member', :locals => {:member => member} %>
    <% end %>
  </tr>
  <% end %>
</table>

and then in the _member partial

<td style="width: 125px">
  <%= h "#{member.full_name(false)}" if member %>
</td>

or whatever your requirements are. This will display a list of members in three columns, top to bottom, left to right, like a phone book.

If anyone discovers something I did poorly or just plain missed, please let me know. Oh, and I have not done any testing/experimenting with passing a block. But since that is just getting passed along, I don't see why it wouldn't work.

Peace,
Phillip

Simpler:

  @members.in_groups_of(3).transpose.each do |group|
    ...
  end

:slight_smile:

Regards,
George.

<span id="disgust" style="voice: charlie_brown">
Good grief!
</span>

Thanks, George. That is much simpler. I wish I had known about that two days ago. I could have saved myself an hour or two of digging and testing.

:slight_smile:

Peace,
Phillip

George wrote:

If anyone discovers something I did poorly or just plain missed,
please let me know. Oh, and I have not done any testing/experimenting
with passing a block. But since that is just getting passed along, I
don't see why it wouldn't work.

Simpler:

  @members.in_groups_of(3).transpose.each do |group|
    ...
  end

:slight_smile:

Regards,
George.

Hi again, George.

As it turns out, transpose is not equivalent to in_vertical_groups_of.
To illustrate, open script/console and do this:

list = (1..10).to_a

=> [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

list.in_groups_of(2)

=> [[1, 2], [3, 4], [5, 6], [7, 8], [9, 10]]

list.in_groups_of(2).transpose

=> [[1, 3, 5, 7, 9], [2, 4, 6, 8, 10]]

Notice that transpose changed the output from five groups of two to two
groups of five. That's not at all what I wanted. It was just
coincidence that it happened to work on my particular example. Now,
here's the output of in_vertical_groups of:

list.in_vertical_groups_of(2)

=> [[1, 6], [2, 7], [3, 8], [4, 9], [5, 10]]

They are still in five groups of two, but the sorting is different.

Peace,
Phillip

George wrote:
>> If anyone discovers something I did poorly or just plain missed,
>> please let me know. Oh, and I have not done any testing/experimenting
>> with passing a block. But since that is just getting passed along, I
>> don't see why it wouldn't work.
>
> Simpler:
>
> @members.in_groups_of(3).transpose.each do |group|
> ...
> end
>
> :slight_smile:
>
> Regards,
> George.

Hi again, George.

As it turns out, transpose is not equivalent to in_vertical_groups_of.
To illustrate, open script/console and do this:

>> list = (1..10).to_a
=> [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
>> list.in_groups_of(2)
=> [[1, 2], [3, 4], [5, 6], [7, 8], [9, 10]]
>> list.in_groups_of(2).transpose
=> [[1, 3, 5, 7, 9], [2, 4, 6, 8, 10]]

Notice that transpose changed the output from five groups of two to two
groups of five. That's not at all what I wanted. It was just
coincidence that it happened to work on my particular example. Now,
here's the output of in_vertical_groups of:

>> list.in_vertical_groups_of(2)
=> [[1, 6], [2, 7], [3, 8], [4, 9], [5, 10]]

They are still in five groups of two, but the sorting is different.

class Array
  def in_vertical_groups_of(n)
    in_groups_of(size/n).transpose
  end
end

Peace,
Phillip

--Greg

Gregory Seidman wrote:

> ...
To illustrate, open script/console and do this:
coincidence that it happened to work on my particular example. Now,
here's the output of in_vertical_groups of:

>> list.in_vertical_groups_of(2)
=> [[1, 6], [2, 7], [3, 8], [4, 9], [5, 10]]

They are still in five groups of two, but the sorting is different.

class Array
  def in_vertical_groups_of(n)
    in_groups_of(size/n).transpose
  end
end

Peace,
Phillip

--Greg

Hi Greg,

Wow, that would be nice and concise, if it worked all the time :slight_smile:

list = (1..10).to_a

=> [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

Works here...

n = 2

=> 2

list.in_groups_of(n)

=> [[1, 2], [3, 4], [5, 6], [7, 8], [9, 10]]

list.in_vertical_groups_of(n)

=> [[1, 6], [2, 7], [3, 8], [4, 9], [5, 10]]

list.in_groups_of(list.size/n).transpose

=> [[1, 6], [2, 7], [3, 8], [4, 9], [5, 10]]

Doesn't work here...

n = 3

=> 3

list.in_groups_of(n)

=> [[1, 2, 3], [4, 5, 6], [7, 8, 9], [10, nil, nil]]

list.in_vertical_groups_of(n)

=> [[1, 5, 9], [2, 6, 10], [3, 7, nil], [4, 8, nil]]

list.in_groups_of(list.size/n).transpose

=> [[1, 4, 7, 10], [2, 5, 8, nil], [3, 6, 9, nil]]

nor when n = 4.

As much as I'd like a shorter, cleaner way of doing it, it looks like my
method will have to suffice for now. Thanks for trying to slim down my
code.

Peace,
Phillip

Gregory Seidman wrote:
>> > ...
>> To illustrate, open script/console and do this:
>> coincidence that it happened to work on my particular example. Now,
>> here's the output of in_vertical_groups of:
>>
>> >> list.in_vertical_groups_of(2)
>> => [[1, 6], [2, 7], [3, 8], [4, 9], [5, 10]]
>>
>> They are still in five groups of two, but the sorting is different.
>
> class Array
> def in_vertical_groups_of(n)
> in_groups_of(size/n).transpose
> end
> end
>
>> Peace,
>> Phillip
> --Greg

Hi Greg,

Wow, that would be nice and concise, if it worked all the time :slight_smile:

>> list = (1..10).to_a
=> [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

Works here...

>> n = 2
=> 2
>> list.in_groups_of(n)
=> [[1, 2], [3, 4], [5, 6], [7, 8], [9, 10]]
>> list.in_vertical_groups_of(n)
=> [[1, 6], [2, 7], [3, 8], [4, 9], [5, 10]]
>> list.in_groups_of(list.size/n).transpose
=> [[1, 6], [2, 7], [3, 8], [4, 9], [5, 10]]

Doesn't work here...

>> n = 3
=> 3
>> list.in_groups_of(n)
=> [[1, 2, 3], [4, 5, 6], [7, 8, 9], [10, nil, nil]]
>> list.in_vertical_groups_of(n)
=> [[1, 5, 9], [2, 6, 10], [3, 7, nil], [4, 8, nil]]
>> list.in_groups_of(list.size/n).transpose
=> [[1, 4, 7, 10], [2, 5, 8, nil], [3, 6, 9, nil]]

nor when n = 4.

As much as I'd like a shorter, cleaner way of doing it, it looks like my
method will have to suffice for now. Thanks for trying to slim down my
code.

Ah, you just didn't give full requirements, i.e. what the output should be
if the size did not divide evenly. Try this:

class Array
  def in_vertical_groups_of(n)
    in_groups_of((size.to_f/n).ceil).transpose
  end
end

Peace,
Phillip

--Greg

Ah, you just didn’t give full requirements, i.e. what the output should be

if the size did not divide evenly. Try this:

In my first message, I said:

This allows you to iterate over the groups and, for example, display

a list of things in a tabular format.

1 2 3

4 5 6

7

I thought this was pretty cool, but wanted to go vertically down the

column first, instead of horizontally

1 4 7

2 5

3 6

Those were pretty much the requirements.

class Array

def in_vertical_groups_of(n)

in_groups_of((size.to_f/n).ceil).transpose

end

end

This does, in fact, work. Thanks. I’ll replace my looping code with someone else’s looping code :slight_smile:

For anyone following along, you might want to now replace my in_vertical_groups_of with this new one:

def in_vertical_groups_of(number, fill_with = nil, &block)

return in_groups_of((size.to_f / number).ceil, fill_with, &block).transpose

end

And then send Greg a +1 email!

Peace,

Phillip

–Greg

Until my next poorly contrived contribution to the community…

Phillip