Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Array grouping methods with index #556

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
87 changes: 87 additions & 0 deletions activesupport/lib/active_support/core_ext/array/grouping.rb
Expand Up @@ -35,7 +35,47 @@ def in_groups_of(number, fill_with = nil)
groups
end
end

# Splits or iterates over the array with an index (which start from 0)
# in groups of size +number+, padding any remaining slots
# with +fill_with+ unless it is +false+. If no block is given returns
# an array of [group, index] arrays.
#
# %w(1 2 3 4 5 6 7).with_index_in_groups_of(3) {|group, index| p "#{group} : #{index}"}
# ["1", "2", "3"] : 0
# ["4", "5", "6"] : 1
# ["7", nil, nil] : 2
#
# %w(1 2 3).with_index_in_groups_of(2, ' ') {|group, index| p "#{group} : #{index}"}
# ["1", "2"] : 0
# ["3", " "] : 1
#
# %w(1 2 3).with_index_in_groups_of(2, false) {|group, index| p "#{group} : #{index}"}
# ["1", "2"] : 0
# ["3"] : 1
#
# %w(1 2 3).with_index_in_groups_of(2) #=> [ [["1", "2"], 0], [["3", nil, nil], 1] ]
def with_index_in_groups_of(number, fill_with = nil)
if fill_with == false
collection = self
else
# size % number gives how many extra we have;
# subtracting from number gives how many to add;
# modulo number ensures we don't add group of just fill.
padding = (number - size % number) % number
collection = dup.concat([fill_with] * padding)
end

groups = []
collection.each_slice(number) { |group| groups << group }

if block_given?
groups.each_with_index { |slice, index| yield(slice, index) }
else
groups.each_with_index.to_a
end
end

# Splits or iterates over the array in +number+ of groups, padding any
# remaining slots with +fill_with+ unless it is +false+.
#
Expand Down Expand Up @@ -78,6 +118,53 @@ def in_groups(number, fill_with = nil)
groups
end
end

# Splits or iterates over the array in +number+ of groups with an index
# (which start from 0), padding any remaining slots with +fill_with+
# unless it is +false+. If no block is given returns an array of
# +number+ [group, index] arrays.
#
# %w(1 2 3 4 5 6 7 8 9 10).with_index_in_groups(3) {|group, index| p "#{group} : #{index}"}
# ["1", "2", "3", "4"] : 0
# ["5", "6", "7", nil] : 1
# ["8", "9", "10", nil] : 2
#
# %w(1 2 3 4 5 6 7).with_index_in_groups(3, '&nbsp;') {|group, index| p "#{group} : #{index}"}
# ["1", "2", "3"] : 0
# ["4", "5", "&nbsp;"] : 1
# ["6", "7", "&nbsp;"] : 2
#
# %w(1 2 3 4 5 6 7).with_index_in_groups(3, false) {|group, index| p "#{group} : #{index}"}
# ["1", "2", "3"] : 0
# ["4", "5"] : 1
# ["6", "7"] : 2
#
# %w(1 2 3).with_index_in_groups(2, false) #=> [ [["1", "2"], 0], [["3"], 1] ]
def with_index_in_groups(number, fill_with = nil)
# size / number gives minor group size;
# size % number gives how many objects need extra accommodation;
# each group hold either division or division + 1 items.
division = size / number
modulo = size % number

# create a new array avoiding dup
groups = []
start = 0

number.times do |index|
length = division + (modulo > 0 && modulo > index ? 1 : 0)
padding = fill_with != false &&
modulo > 0 && length == division ? 1 : 0
groups << slice(start, length).concat([fill_with] * padding)
start += length
end

if block_given?
groups.each_with_index { |g, i| yield(g, i) }
else
groups.each_with_index.to_a
end
end

# Divides the array into one or more subarrays based on a delimiting +value+
# or the result of an optional block.
Expand Down
92 changes: 92 additions & 0 deletions activesupport/test/core_ext/array_ext_test.rb
Expand Up @@ -118,6 +118,7 @@ def test_in_groups_of_with_perfect_fit

def test_in_groups_of_with_padding
groups = []

('a'..'g').to_a.in_groups_of(3) do |group|
groups << group
end
Expand All @@ -144,6 +145,55 @@ def test_in_groups_of_without_padding

assert_equal [%w(a b c), %w(d e f), ['g']], groups
end

def test_with_index_in_groups_of_with_perfect_fit
groups = {}

('a'..'i').to_a.with_index_in_groups_of(3) do |group, index|
groups[index] = group
end

indexed = {0 => %w(a b c), 1 => %w(d e f), 2 => %w(g h i)}

assert_equal indexed, groups
assert_equal [[%w(a b c), 0], [%w(d e f), 1], [%w(g h i), 2]], ('a'..'i').to_a.with_index_in_groups_of(3)
end

def test_with_index_in_groups_of_with_padding
groups = {}

('a'..'g').to_a.with_index_in_groups_of(3) do |group, index|
groups[index] = group
end

indexed = {0 => %w(a b c), 1 => %w(d e f), 2 => ['g', nil, nil]}

assert_equal indexed, groups
end

def test_with_index_in_groups_of_pads_with_specified_values
groups = {}

('a'..'g').to_a.with_index_in_groups_of(3, 'foo') do |group, index|
groups[index] = group
end

indexed = {0 => %w(a b c), 1 => %w(d e f), 2 => ['g', 'foo', 'foo']}

assert_equal indexed, groups
end

def test_with_index_in_groups_of_without_padding
groups = {}

('a'..'g').to_a.with_index_in_groups_of(3, false) do |group, index|
groups[index] = group
end

indexed = {0 => %w(a b c), 1 => %w(d e f), 2 => ['g']}

assert_equal indexed, groups
end

def test_in_groups_returned_array_size
array = (1..7).to_a
Expand Down Expand Up @@ -186,6 +236,48 @@ def test_in_groups_without_padding
assert_equal [[1, 2, 3], [4, 5], [6, 7]],
(1..7).to_a.in_groups(3, false)
end

def test_with_index_in_groups_returned_array_size
array = (1..7).to_a

1.upto(array.size + 1) do |number|
assert_equal number, array.with_index_in_groups(number).size
end
end

def test_with_index_in_groups_with_empty_array
assert_equal [[[], 0], [[],1], [[],2]], [].with_index_in_groups(3)
end

def test_with_index_in_groups_with_block
array = (1..9).to_a
groups = []

array.with_index_in_groups(3) do |group, index|
groups << [group, index]
end

assert_equal array.with_index_in_groups(3), groups
end

def test_with_index_in_groups_with_perfect_fit
assert_equal [[[1, 2, 3], 0], [[4, 5, 6], 1], [[7, 8, 9], 2]],
(1..9).to_a.with_index_in_groups(3)
end

def test_with_index_in_groups_with_padding
array = (1..7).to_a

assert_equal [[[1, 2, 3], 0], [[4, 5, nil], 1], [[6, 7, nil], 2]],
array.with_index_in_groups(3)
assert_equal [[[1, 2, 3], 0], [[4, 5, 'foo'], 1], [[6, 7, 'foo'], 2]],
array.with_index_in_groups(3, 'foo')
end

def test_with_index_in_groups_without_padding
assert_equal [[[1, 2, 3], 0], [[4, 5], 1], [[6, 7], 2]],
(1..7).to_a.with_index_in_groups(3, false)
end
end

class ArraySplitTests < Test::Unit::TestCase
Expand Down