Reputation: 5172
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
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
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
Reputation: 44685
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]]
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.
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)
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
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
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
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