8

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

4
  • I think you lost 8 in your result. :) Commented Dec 14, 2015 at 1:57
  • Are you concerned only with arrays having nine elements? If not, you'll have to tell us the rule you want implemented? Commented Dec 14, 2015 at 2:33
  • @BroiSatse yep indeed (: Commented Dec 16, 2015 at 21:07
  • @CarySwoveland not at all Commented Dec 16, 2015 at 21:07

6 Answers 6

18

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.

Sign up to request clarification or add additional context in comments.

2 Comments

I'm unable to get your pure Ruby solution working. For me your each_slice block is returning nil.
@6ftDan - Thanks for noticing, fixed now.
5

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.

1 Comment

I somehow missed your solution before. Very creative!
3

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

2 Comments

I suggest you either add the line output at the end or, better, delete output = [] and change the 4th line to input.each_with_index.with_object([]) do | (val, index), output|.
Changed it to match your suggestion, I did not know about the with_object method, seems very useful!
1

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.

2 Comments

well as long it remains iteratable… it is an 'array of arrays'
behind the scene , yes :)
1

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]] 

Comments

1

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

2 Comments

You could write it: arr.each_with_index.with_object([]) { |(e,i),a| (a[i%n] ||= []) << e }.
@CarySwoveland Good suggestion. It avoids the additional statement a to return the accumulated object when using reduce(). Will use it next time.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.