Splitting an array into "even" parts

T

Tore Aursand

Hi there!

I need to split an array into "even" parts, so that each resulting array
contains the same number of elements.

Think of it as splitting a list of many names into multiple columns, and
you want x numbers of names in each column so that each column gets filled
up even-wise.

The problem arise, of course, when you have 10 elements and want to split
those into 3 columns. That should be listed like this:

+-------+-------+-------+
| Col 1 | Col 2 | Col 3 |
+-------+-------+-------+
| 1 | 5 | 9 |
| 2 | 6 | 10 |
| 3 | 7 | |
| 4 | 8 | |
+-------+-------+-------+

So far I've come up with this solution:

sub split_array {
my $array = shift; # Array reference
my $parts = shift; # Number of columns to split into

## Make sure $parts is set (default = 2), and that it isn't
## larger than the number of elements in $array
$parts ||= 2;
$parts = @$array if ( $parts > @$array );

## Split
my @split = (); # Will contain the splitted data
my $slice = POSIX::ceil( @$array / $parts ); # Size of each part

for ( 1 .. $parts ) {
if ( $_ == $parts ) {
push( @split, $array );
}
else {
push( @split, splice(@$array, 0, $slice) );
}
}

## Return
return \@split;
}

This code is doubtly the most efficient (I'm always looking for
efficient, yet readable, ways to do things), and I haven't run that many
tests on it either. Any comments on it?
 
A

Andreas Kahari

Hi there!

I need to split an array into "even" parts, so that each resulting array
contains the same number of elements. [cut]
This code is doubtly the most efficient (I'm always looking for
efficient, yet readable, ways to do things), and I haven't run that many
tests on it either. Any comments on it?

Here's something that doesn't rely on external modules:

sub split_array
{
my $arr = shift;
my $bins = shift;

my $size = int((scalar @{ $arr })/$bins);

if (scalar @{ $arr } % $bins != 0) {
++$size;
}

my @result;

for my $i (0 .. ($bins - 1)) {
push @result, [ @{ $arr }[$i*$size .. (($i + 1)*$size - 1)] ];
}

return @result;
}
 
E

Eric J. Roode

-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA1

Hi there!

I need to split an array into "even" parts, so that each resulting
array contains the same number of elements.

Think of it as splitting a list of many names into multiple columns,
and you want x numbers of names in each column so that each column
gets filled up even-wise.

The problem arise, of course, when you have 10 elements and want to
split those into 3 columns. That should be listed like this:

+-------+-------+-------+
| Col 1 | Col 2 | Col 3 |
+-------+-------+-------+
| 1 | 5 | 9 |
| 2 | 6 | 10 |
| 3 | 7 | |
| 4 | 8 | |
+-------+-------+-------+

So far I've come up with this solution:

sub split_array {
my $array = shift; # Array reference
my $parts = shift; # Number of columns to split into

## Make sure $parts is set (default = 2), and that it isn't
## larger than the number of elements in $array
$parts ||= 2;
$parts = @$array if ( $parts > @$array );

## Split
my @split = (); # Will contain the splitted data
my $slice = POSIX::ceil( @$array / $parts ); # Size of each part

for ( 1 .. $parts ) {
if ( $_ == $parts ) {
push( @split, $array );
}
else {
push( @split, splice(@$array, 0, $slice) );
}
}

## Return
return \@split;
}

This code is doubtly the most efficient (I'm always looking for
efficient, yet readable, ways to do things), and I haven't run that
many tests on it either. Any comments on it?

aplice() pretty much does what you want by itself -- there's no need for
all the special-case-handling code you are surrounding it with.

sub split_array
{
my $array = shift;
my $parts = shift || 2;
my @array = @$array; # working copy
my @split; # result

while (@array)
{
push @split, [splice @array, 0, $parts];
}
return \@split;
}

or even:

push @split, [splice @array, 0, $parts] while @array;

- --
Eric
$_ = reverse sort $ /. r , qw p ekca lre uJ reh
ts p , map $ _. $ " , qw e p h tona e and print

-----BEGIN PGP SIGNATURE-----
Version: PGPfreeware 7.0.3 for non-commercial use <http://www.pgp.com>

iQA/AwUBP6jsRmPeouIeTNHoEQJN/gCdG6n983iPY7jp14peWtV09TyG21AAnRH1
pu+8ZH/a6uRkk+sUbRLGbfx0
=fmD3
-----END PGP SIGNATURE-----
 
T

Tore Aursand

sub split_array
{
my $array = shift;
my $parts = shift || 2;
my @array = @$array; # working copy
my @split; # result

while (@array)
{
push @split, [splice @array, 0, $parts];
}
return \@split;
}

Fine solution, indeed, but still not satisfying me needs. Let's take a
look at an example:

my @array = ( 1..9 );
my $parts = 4;

Expected result should be:

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

But your function only returns 3 arrays. A note begins to form in my back
head: Make sure that @array is splittable in $parts parts. :)
 
A

Anno Siegel

Tore Aursand said:
sub split_array
{
my $array = shift;
my $parts = shift || 2;
my @array = @$array; # working copy
my @split; # result

while (@array)
{
push @split, [splice @array, 0, $parts];
}
return \@split;
}

Fine solution, indeed, but still not satisfying me needs. Let's take a
look at an example:

my @array = ( 1..9 );
my $parts = 4;

Expected result should be:

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

Well, here's a variant that does that. It returns the array of split
parts directly, not a reference to it, but that's trivial to fix.

sub split_array {
my $parts = shift || 2;
my @array = @{ shift()};
my $size = int @array/$parts;
my $rem = @array % $parts;
map [splice @array, 0, $size + ($rem-- > 0)], 1 .. $parts;
}


Anno
 
T

Tore Aursand

sub split_array {
my $parts = shift || 2;
my @array = @{ shift()};
my $size = int @array/$parts;
my $rem = @array % $parts;
map [splice @array, 0, $size + ($rem-- > 0)], 1 .. $parts;
}

Most excellent. Seems to do excactly what I am looking for. Thanks a
lot, Anno!
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,755
Messages
2,569,537
Members
45,021
Latest member
AkilahJaim

Latest Threads

Top