Skip to content

Instantly share code, notes, and snippets.

@mcrumley
Created April 10, 2014 15:56
Show Gist options
  • Star 3 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save mcrumley/10396818 to your computer and use it in GitHub Desktop.
Save mcrumley/10396818 to your computer and use it in GitHub Desktop.
Partition an array into approximately equal-sized chunks
<?php
/**
* Partition an array into N chunks
*
* @param array $a array to split
* @param int $np number of partitions
* @param bool $pad pad the result with empty arrays if necessary (default = true)
*
* @return array the original array split into $np chunks
*
* Examples:
* array_partition(array(1, 2, 3), 2) -> array(array(1, 2), array(3))
* array_partition(array(1, 2, 3), 4) -> array(array(1), array(2), array(3), array())
* array_partition(array(1, 2, 3), 4, false) -> array(array(1), array(2), array(3))
*/
function array_partition(array $a, $np, $pad = true)
{
$np = (int)$np;
if ($np <= 0) {
trigger_error('partition count must be greater than zero', E_USER_NOTICE);
return array();
}
$c = count($a);
$per_array = (int)floor($c / $np);
$rem = $c % $np;
// special case for an empty array
if ($c === 0) {
if ($pad) {
$result = array_fill(0, $np, array());
} else {
$result = array();
}
}
// array_chunk will work if the remainder is 0 or np-1, or if there are more partitions than elements in the array
elseif ($rem === 0 || $rem == $np - 1 || $np >= $c) {
// if there is a remainder each partition will need 1 more
$result = array_chunk($a, $per_array + ($rem > 0 ? 1 : 0));
// if necessary, pad out the array with empty arrays
if ($pad && $np > $c) {
$result = array_merge($result, array_fill(0, $np - $c, array()));
}
}
// use the slower case if 0 < remainder < np-1 and there are more elements in the array than paritions
// ($rem > 0 && $rem < $np - 1 && $np < $c)
else {
$split = $rem * ($per_array + 1);
// the first $rem partitions will have $per_array + 1
$result = array_chunk(array_slice($a, 0, $split), $per_array+1);
// the rest of the partitions will have per_array
$result = array_merge($result, array_chunk(array_slice($a, $split), $per_array));
// no padding is necessary if the conditions for this case are met
}
return $result;
}
function check_array_partition(array $a, $np, array $orig) {
// make sure the partition count is correct
if (count($a) !== $np) {
return false;
}
// accumulator - rebuild the original array
$acc = array();
// remember the first partition size
$last = count($a) ? count($a[0]) : 0;
// go through each partition
foreach ($a as $part) {
// make sure the partition size never decreases by more than 1
if (count($part) > $last || count($part) < $last - 1) {
echo 'array_partition(array['.count($orig).'], '.$np.') FAILED';
return false;
}
foreach ($part as $int) {
$acc []= $int;
}
$last = count($part);
}
// make sure all the values are in the array and in the correct order
if ($np > 0 && $orig === $acc) {
return true;
// if partition count is zero, there should be nothing in the accumulator
} elseif ($np === 0 && $acc === array()) {
return true;
} else {
echo 'array_partition(array['.count($orig).'], '.$np.') FAILED';
return false;
}
}
foreach (range(0, 10) as $partitions) {
if ($partitions === 0) {
error_reporting(~E_USER_NOTICE);
} else {
error_reporting(E_ALL);
}
check_array_partition(array_partition(array(), $partitions), $partitions, array());
check_array_partition(array_partition(array(0), $partitions), $partitions, array(0));
foreach (range(1, 6) as $array_size) {
check_array_partition(array_partition(range(0, $array_size), $partitions), $partitions, range(0, $array_size));
}
}
echo ' DONE';
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment