Ben
Ben

Reputation: 5172

ruby / splitting array into columns

I could've found out using the right naming, but so far nothing popped up. I'd look into splitting an array into columns.

Meaning, having [0,1,2,3,4,5,6,7,9], I'd like to have 4 arrays (4 columns) :

[ [0,4,9], [1,5], [2,6], [3,7] ]

Practically, I'd look into iterating over an active record array, displaying those (they're images records) in 4 different html columns, while maintaining their order from left to right

Upvotes: 8

Views: 3000

Answers (6)

sschmeck
sschmeck

Reputation: 7685

A pure Ruby solution.

def split(n, arr)
  arr.each_with_index.reduce(Array.new(n) { [] }) do |a, (e, i)|
    a[i%n] << e
    a
  end
end

UPDATE: As oneliner as suggested by Cary. It uses with_object() instead of reduce() and initializes the sub array lazily when used.

def split(n, arr)
  arr.each_with_index.with_object([]) { |(e, i), a| (a[i%n] ||= []) << e }
end

Upvotes: 1

6ft Dan
6ft Dan

Reputation: 2435

I have a short solution :-)

x = [0, 1, 2, 3, 4, 5, 6, 7, 9]

x.group_by.with_index {|_,index| index % 4 }.values
# => [[0, 4, 9], [1, 5], [2, 6], [3, 7]]

I have posted a blog post on Ruby: Arrays by Example for a visual resource.

Upvotes: 5

BroiSatse
BroiSatse

Reputation: 44685

Rails solution

In rails, you can simply do:

ary = [0,1,2,3,4,5,6,7,8,9]
ary.in_groups_of(4).transpose.map(&:compact)
  #=> [[0, 4, 8], [1, 5, 9], [2, 6], [3, 7]]

Explanation:

in_groups_of is a cool rails method added to Array class, which behaves very similar to each_slice, with the difference being that it guarantees all the arrays to be the same size. It is important here so we can use transpose later on. This method returns an array of your rows.

Now, transpose - another cool method worth knowing. It expects an array of arrays, and all inner arrays must be the same length (so in fact this represents a rectangular matrix). What it does - it returns an array of columns in target array.

Now we need to get rid of nils (unless they don't bother you), so we run compact on each of the columns and we have exactly what you wanted.

Pure ruby solution:

We don't have in_groups_of so we need to implement same behaviour without it:

ary.each_slice(4).map {|a| a.fill nil, a.size, 4 - a.size}.transpose.map(&:compact)

Better solution to practical issue

Practically, I'd look into iterating over an active record array, displaying those (they're images records) in 4 different html columns, while maintaining their order from left to right

You should never use tables to display your data unless it is a tabular data. Images are not tabular data, meaning that the location of that particular image at given column/row is completely irrelevant. There always is a better way to do this.

In your case, I would suggest pure CSS solution (haml):

%ul.images
  - @images.each do |i|
    %li= image_tag i.iamge_url    # or sth

Then in your css (scss):

ul.images {
  width: 100%;
  font-size: 0;    # remove whitespace between li elements
  li {
    display: inline-block;
    width: 25%;   # forcing 25% without whitespaces guarantees 4 column
  }
}

Main advantage of this model is that you can make the number of column dependent on media queries, so display different number of images per row depending on your user screen size (important for mobiles).

If you feel more adventurous and you hate IE and don't care for it's users, go and check out flex-boxes as well. One day it might become a standard and can be seen on many pages already.

Upvotes: 18

hbejgel
hbejgel

Reputation: 4837

This should do the trick:

input = [0,1,2,3,4,5,6,7,8,9]
max_columns = 4
input.each_with_index.with_object([]) do | (val, index), output|
    i = index % max_columns
    (output[i] ||= []).push(val)
end

Upvotes: 3

Cary Swoveland
Cary Swoveland

Reputation: 110675

I have assumed that if the size of the array is not evenly divided by 4, the first so-many sliced arrays are to be of the same size and each have one more element than each each of remaining sliced arrays. Here are a couple ways you could do that.

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

Compute number of arrays having an extra element

def split_it(n, arr)
  std_arr_size, nbr_bonus_arrays = arr.size.divmod(n)
  nbr_bonus_elements = nbr_bonus_arrays * (std_arr_size + 1)
  arr[0...nbr_bonus_elements].each_slice(std_arr_size+1).to_a +
    arr[nbr_bonus_elements..-1].each_slice(std_arr_size).to_a
end

split_it(1, arr)  #=> [[1, 2, 3, 4, 5, 6, 7, 8, 9]] 
split_it(2, arr)  #=> [[1, 2, 3, 4, 5], [6, 7, 8, 9]] 
split_it(3, arr)  #=> [[1, 2, 3], [4, 5, 6], [7, 8, 9]] 
split_it(4, arr)  #=> [[1, 2, 3], [4, 5], [6, 7], [8, 9]]  
split_it(5, arr)  #=> [[1, 2], [3, 4], [5, 6], [7, 8], [9]] 
split_it(6, arr)  #=> [[1, 2], [3, 4], [5, 6], [7], [8], [9]] 
split_it(7, arr)  #=> [[1, 2], [3, 4], [5], [6], [7], [8], [9]] 
split_it(8, arr)  #=> [[1, 2], [3], [4], [5], [6], [7], [8], [9]] 
split_it(9, arr)  #=> [[1], [2], [3], [4], [5], [6], [7], [8], [9]] 

Use recursion

def split_it(n, arr)
  return [arr] if n==1
  m = (arr.size.to_f/n).ceil
  [arr.first(m)] + split_it(n-1, arr[m..-1])
end

split_it(1, arr)  #=> [[1, 2, 3, 4, 5, 6, 7, 8, 9]] 
split_it(2, arr)  #=> [[1, 2, 3, 4, 5], [6, 7, 8, 9]] 
split_it(3, arr)  #=> [[1, 2, 3], [4, 5, 6], [7, 8, 9]] 
split_it(4, arr)  #=> [[1, 2, 3], [4, 5], [6, 7], [8, 9]]  
split_it(5, arr)  #=> [[1, 2], [3, 4], [5, 6], [7, 8], [9]] 
split_it(6, arr)  #=> [[1, 2], [3, 4], [5, 6], [7], [8], [9]] 
split_it(7, arr)  #=> [[1, 2], [3, 4], [5], [6], [7], [8], [9]] 
split_it(8, arr)  #=> [[1, 2], [3], [4], [5], [6], [7], [8], [9]] 
split_it(9, arr)  #=> [[1], [2], [3], [4], [5], [6], [7], [8], [9]] 

Upvotes: 1

z atef
z atef

Reputation: 7679

or by slicing your big array into smaller arrays with SLICE

input = Array(1 .. 33)

output = input.each_slice(4).to_a

output.each do |x| puts "#{x}" 
end

outputs :

[1, 2, 3, 4]
[5, 6, 7, 8]
[9, 10, 11, 12]
[13, 14, 15, 16]
[17, 18, 19, 20]
[21, 22, 23, 24]
[25, 26, 27, 28]
[29, 30, 31, 32]
[33]

fyi , not exactly an array of arrays , but something to look into.

Upvotes: 1

Related Questions