I have an array of 7 numbers (1,2,3,4,5,6,7) and I want to choose 5 of the numbers like
(1,2,3,4,5), (1,2,3,4,6), (1,2,3,4,7).
Note that (1,2,3,4,5) is equal to (4,5,3,1,2), so only one of those should be included in the output.
I would like to know if there is a function in PHP or any algorithm that can do this ?
I have no idea where to start from.
Can you help me ?
I want all the combinations of 7 given numbers ( they are taken from an array ) put into 5 slots, disregarding order.
You can use the solution found here http://stereofrog.com/blok/on/070910.
Incase the link goes down here's the code....
class Combinations implements Iterator
{
protected $c = null;
protected $s = null;
protected $n = 0;
protected $k = 0;
protected $pos = 0;
function __construct($s, $k) {
if(is_array($s)) {
$this->s = array_values($s);
$this->n = count($this->s);
} else {
$this->s = (string) $s;
$this->n = strlen($this->s);
}
$this->k = $k;
$this->rewind();
}
function key() {
return $this->pos;
}
function current() {
$r = array();
for($i = 0; $i < $this->k; $i++)
$r[] = $this->s[$this->c[$i]];
return is_array($this->s) ? $r : implode('', $r);
}
function next() {
if($this->_next())
$this->pos++;
else
$this->pos = -1;
}
function rewind() {
$this->c = range(0, $this->k);
$this->pos = 0;
}
function valid() {
return $this->pos >= 0;
}
protected function _next() {
$i = $this->k - 1;
while ($i >= 0 && $this->c[$i] == $this->n - $this->k + $i)
$i--;
if($i < 0)
return false;
$this->c[$i]++;
while($i++ < $this->k - 1)
$this->c[$i] = $this->c[$i - 1] + 1;
return true;
}
}
foreach(new Combinations("1234567", 5) as $substring)
echo $substring, ' ';
12345 12346 12347 12356 12357 12367 12456 12457 12467 12567 13456 13457 13467 13567 14567 23456 23457 23467 23567 24567 34567
<?php
echo "<pre>";
$test = array("test_1","test_2","test_3");
// Get Combination
$return = uniqueCombination($test);
//Sort
sort($return);
//Pretty Print
print_r(array_map(function($v){ return implode(",", $v); }, $return));
function uniqueCombination($in, $minLength = 1, $max = 2000) {
$count = count($in);
$members = pow(2, $count);
$return = array();
for($i = 0; $i < $members; $i ++) {
$b = sprintf("%0" . $count . "b", $i);
$out = array();
for($j = 0; $j < $count; $j ++) {
$b{$j} == '1' and $out[] = $in[$j];
}
count($out) >= $minLength && count($out) <= $max and $return[] = $out;
}
return $return;
}
?>
output
Array
(
[0] => test_1
[1] => test_2
[2] => test_3
[3] => test_1,test_2
[4] => test_1,test_3
[5] => test_2,test_3
[6] => test_1,test_2,test_3
)
The Math_Combinatorics in PEAR repository does exactly what you want:
A package that returns all the combinations and permutations, without
repetition, of a given set and subset size. Associative arrays are
preserved.
require_once 'Math/Combinatorics.php';
$combinatorics = new Math_Combinatorics;
$input = array(1, 2, 3, 4, 5, 6, 7);
$output = $combinatorics->combinations($input, 5); // 5 is the subset size
// 1,2,3,4,5
// 1,2,3,4,6
// 1,2,3,4,7
// 1,2,3,5,6
// 1,2,3,5,7
// 1,2,3,6,7
// 1,2,4,5,6
// 1,2,4,5,7
// 1,2,4,6,7
// 1,2,5,6,7
// 1,3,4,5,6
// 1,3,4,5,7
// 1,3,4,6,7
// 1,3,5,6,7
// 1,4,5,6,7
// 2,3,4,5,6
// 2,3,4,5,7
// 2,3,4,6,7
// 2,3,5,6,7
// 2,4,5,6,7
// 3,4,5,6,7
Another solution that bases on stack. It's quit fast but eats much memory.
Hope that helps someone.
In detail:
function _combine($numbers, $length)
{
$combinations = array();
$stack = array();
// every combinations can be ordered
sort($numbers);
// startup
array_push($stack, array(
'store' => array(),
'options' => $numbers,
));
while (true) {
// pop a item
$item = array_pop($stack);
// end of stack
if (!$item) {
break;
}
// valid store
if ($length <= count($item['store'])) {
$combinations[] = $item['store'];
continue;
}
// bypass when options are not enough
if (count($item['store']) + count($item['options']) < $length) {
continue;
}
foreach ($item['options'] as $index => $n) {
$newStore = $item['store'];
$newStore[] = $n;
// every combine can be ordered
// so accept only options which is greater than store numbers
$newOptions = array_slice($item['options'], $index + 1);
// push new items
array_push($stack, array(
'store' => $newStore,
'options' => $newOptions,
));
}
}
return $combinations;
}
Improved this answer to work with associative array as well:
function uniqueCombination($values, $minLength = 1, $maxLength = 2000) {
$count = count($values);
$size = pow(2, $count);
$keys = array_keys($values);
$return = [];
for($i = 0; $i < $size; $i ++) {
$b = sprintf("%0" . $count . "b", $i);
$out = [];
for($j = 0; $j < $count; $j ++) {
if ($b[$j] == '1') {
$out[$keys[$j]] = $values[$keys[$j]];
}
}
if (count($out) >= $minLength && count($out) <= $maxLength) {
$return[] = $out;
}
}
return $return;
}
Eg:
print_r(uniqueCombination([
'a' => 'xyz',
'b' => 'pqr',
]);
Result:
Array
(
[0] => Array
(
[b] => pqr
)
[1] => Array
(
[a] => xyz
)
[2] => Array
(
[a] => xyz
[b] => pqr
)
)
It will still work for non-associative arrays:
print_r(uniqueCombination(['a', 'b']);
Result:
Array
(
[0] => Array
(
[1] => b
)
[1] => Array
(
[0] => a
)
[2] => Array
(
[0] => a
[1] => b
)
)
New solution which optimizes speed and memory for combining algorithm
Mindset: generate combinations K numbers of Array of numbers. New solution will use K 'for' statements. One 'for' One number.
Such as: $K = 5 mean that 5 of 'for' statements is used
$total = count($array);
$i0 = -1;
for ($i1 = $i0 + 1; $i1 < $total; $i1++) {
for ($i2 = $i1 + 1; $i2 < $total; $i2++) {
for ($i3 = $i2 + 1; $i3 < $total; $i3++) {
for ($i4 = $i3 + 1; $i4 < $total; $i4++) {
for ($i5 = $i4 + 1; $i5 < $total; $i5++) {
$record = array();
for ($i = 1; $i <= $k; $i++) {
$t = "i$i";
$record[] = $array[$$t];
}
$callback($record);
}
}
}
}
}
And detail of code that generated the real code that will be execute by eval() function
function combine($array, $k, $callback)
{
$total = count($array);
$init = '
$i0 = -1;
';
$sample = '
for($i{current} = $i{previous} + 1; $i{current} < $total; $i{current}++ ) {
{body}
}
';
$do = '
$record = array();
for ($i = 1; $i <= $k; $i++) {
$t = "i$i";
$record[] = $array[$$t];
}
$callback($record);
';
$for = '';
for ($i = $k; $i >= 1; $i--) {
switch ($i) {
case $k:
$for = str_replace(['{current}', '{previous}', '{body}'], [$i, $i - 1, $do], $sample);
break;
case 1:
$for = $init . str_replace(['{current}', '{previous}', '{body}'], [$i, $i - 1, $for], $sample);
break;
default:
$for = str_replace(['{current}', '{previous}', '{body}'], [$i, $i - 1, $for], $sample);
break;
}
}
// execute
eval($for);
}
How to combine K numbers of Array
$k = 5;
$array = array(1, 2, 3, 4, 5, 6, 7);
$callback = function ($record) {
echo implode($record) . "\n";
};
combine($array, $k, $callback);
I found the other answers here confusing or overly complicated, so I wrote my own. I think this is a simple solution with a recursive method. The basic idea is that you go through your array and for each item decide whether or not it is in the combination (actually, you don't decide, you recursively try both ways). You make this choice for the first item and then combine it with the recursively generated combinations of the rest of the array. This solution fills a result array with every combination of your array as a sub-array. It uses the items in order and it preserves associations, including with numeric keys.
function combinations(array $items, int $numToChoose, array &$results, $comb = []): void {
if (count($items) < $numToChoose) {
throw new \Exception("Asked to choose $numToChoose items from an array of length ". count($items));
}
// if nothing left to choose, we have a complete combination
if ($numToChoose === 0) {
$results[] = $comb;
return;
}
// if we have to choose everything at this point, then we know what to do
if (count($items) == $numToChoose) {
$results[] = $comb + $items;
return;
}
// The recursive cases: either use the first element or not and find combinations of the rest
$val = reset($items);
$key = key($items);
unset($items[$key]);
// not using it
combinations($items, $numToChoose, $results, $comb);
// using it
$comb[$key] = $val;
combinations($items, $numToChoose - 1, $results, $comb);
}
// Do a test run
$combs = [];
combinations([1=>1, 2=>2, 3=>3], 2, $combs);
var_dump($perms);
This results in the output:
array(3) {
[0]=>
array(2) {
[2]=>
int(2)
[3]=>
int(3)
}
[1]=>
array(2) {
[1]=>
int(1)
[3]=>
int(3)
}
[2]=>
array(2) {
[1]=>
int(1)
[2]=>
int(2)
}
}
I needed a combining function that included subsets, so I took #Nguyen Van Vinh's answer and modified it for my needs.
If you pass [1,2,3,4] to the function, it returns every unique combination and subset, sorted:
[
[1,2,3,4], [1,2,3], [1,2,4], [1,3,4], [2,3,4], [1,2], [1,3], [1,4], [2,3], [2,4], [3,4], [1], [2], [3], [4]
]
Here's the function:
function get_combinations_with_length( $numbers, $length ){
$result = array();
$stack = array();
// every combinations can be ordered
sort($numbers);
// startup
array_push($stack, array(
'store' => array(),
'options' => $numbers,
));
while (true) {
// pop a item
$item = array_pop($stack);
// end of stack
if (!$item) break;
// valid store
if ($length <= count($item['store'])) {
$result[] = $item['store'];
continue;
}
// bypass when options are not enough
if (count($item['store']) + count($item['options']) < $length) {
continue;
}
foreach ($item['options'] as $i=>$n) {
$newStore = $item['store'];
$newStore[] = $n;
// every combine can be ordered, so accept only options that are greater than store numbers
$newOptions = array_slice($item['options'], $i + 1);
// array_unshift to sort numerically, array_push to reverse
array_unshift($stack, array(
'store' => $newStore,
'options' => $newOptions,
));
}
}
return $result;
}
function get_all_combinations( $numbers ){
$length = count($numbers);
$result = [];
while ($length > 0) {
$result = array_merge($result, get_combinations_with_length( $numbers, $length ));
$length--;
}
return $result;
}
$numbers = [1,2,3,4];
$result = get_all_combinations($numbers);
echo 'START: '.json_encode( $numbers ).'<br><br>';
echo 'RESULT: '.json_encode( $result ).'<br><br>';
echo '('.count($result).' combination subsets found)';
Related
I have the following code
<?php
function randomGen($min, $max, $quantity) {
$numbers = range($min, $max);
shuffle($numbers);
$tmp = array_slice($numbers, 0, $quantity);
//sort($tmp);
return $tmp;
}
echo '<pre>';
$arr = randomGen(2,7,5);
//sort($arr);}
print_r($arr);
This code generates random n numbers between the specified range.
However, it returns the output similar to the following many times
Array
(
[0] => 3
[1] => 4
[2] => 7
[3] => 5
[4] => 2
)
Here, 2 3 4 5 are consecutive numbers. I am trying to modify the above code so that it does not return more than 3 consecutive numbers. The output should be similar to this
Array
(
[0] => 2
[1] => 3
[2] => 5
[3] => 6
[4] => 7
)
As you can see, it has just 3 consecutive numbers 5 6 7.
I have tried the following
<?php
function randomGen($min, $max, $quantity) {
$numbers = range($min, $max);
shuffle($numbers);
$tmp = array_slice($numbers, 0, $quantity);
sort($tmp);
return $tmp;
}
echo '<pre>';
$arr = randomGen(2,7,5);
$count = 0;
$first = $arr[0];
$i = 1;
//for($i = 1; $i < count($arr); $i++) {
while ($i < count($arr)) {
$ele = $arr[$i];
if ($counter >=3) {
$counter = 0;
$arr = randomGen(2,7,5);
$first = $arr[0];
$i = 1;
}
else {
if ($ele - $first === 1) {
$counter++;
//continue;
}
$first = $ele;
}
$i++;
}
print_r($arr);
But it doesn't provide the expected output.
Thanks in advance for any help.
Please see if we can do something like this
function randomGen($min, $max, $quantity,$rangestep) {
$numbers = range($min, $max,$rangestep);
$tmp = array_slice($numbers, 0, $quantity);
sort($tmp);
shuffle($tmp);
return $tmp;
}
echo '<pre>';
$arr = randomGen(2,7,5,1);
$count = 0;
$first = $arr[0];
$i = 1;
//for($i = 1; $i < count($arr); $i++) {
$counter = 0;
while ($i < count($arr)) {
$ele = $arr[$i];
if ($counter >=3) {
$counter = 0;
$arr = randomGen(2,7,8,2);
$first = $arr[0];
$i = 1;
}
else {
if ($ele - $first === 1) {
$counter++;
//continue;
}
$first = $ele;
}
$i++;
}
print_r($arr);
die;
I have an array of 7 numbers (1,2,3,4,5,6,7) and I want to choose 5 of the numbers like
(1,2,3,4,5), (1,2,3,4,6), (1,2,3,4,7).
Note that (1,2,3,4,5) is equal to (4,5,3,1,2), so only one of those should be included in the output.
I would like to know if there is a function in PHP or any algorithm that can do this ?
I have no idea where to start from.
Can you help me ?
I want all the combinations of 7 given numbers ( they are taken from an array ) put into 5 slots, disregarding order.
You can use the solution found here http://stereofrog.com/blok/on/070910.
Incase the link goes down here's the code....
class Combinations implements Iterator
{
protected $c = null;
protected $s = null;
protected $n = 0;
protected $k = 0;
protected $pos = 0;
function __construct($s, $k) {
if(is_array($s)) {
$this->s = array_values($s);
$this->n = count($this->s);
} else {
$this->s = (string) $s;
$this->n = strlen($this->s);
}
$this->k = $k;
$this->rewind();
}
function key() {
return $this->pos;
}
function current() {
$r = array();
for($i = 0; $i < $this->k; $i++)
$r[] = $this->s[$this->c[$i]];
return is_array($this->s) ? $r : implode('', $r);
}
function next() {
if($this->_next())
$this->pos++;
else
$this->pos = -1;
}
function rewind() {
$this->c = range(0, $this->k);
$this->pos = 0;
}
function valid() {
return $this->pos >= 0;
}
protected function _next() {
$i = $this->k - 1;
while ($i >= 0 && $this->c[$i] == $this->n - $this->k + $i)
$i--;
if($i < 0)
return false;
$this->c[$i]++;
while($i++ < $this->k - 1)
$this->c[$i] = $this->c[$i - 1] + 1;
return true;
}
}
foreach(new Combinations("1234567", 5) as $substring)
echo $substring, ' ';
12345 12346 12347 12356 12357 12367 12456 12457 12467 12567 13456 13457 13467 13567 14567 23456 23457 23467 23567 24567 34567
<?php
echo "<pre>";
$test = array("test_1","test_2","test_3");
// Get Combination
$return = uniqueCombination($test);
//Sort
sort($return);
//Pretty Print
print_r(array_map(function($v){ return implode(",", $v); }, $return));
function uniqueCombination($in, $minLength = 1, $max = 2000) {
$count = count($in);
$members = pow(2, $count);
$return = array();
for($i = 0; $i < $members; $i ++) {
$b = sprintf("%0" . $count . "b", $i);
$out = array();
for($j = 0; $j < $count; $j ++) {
$b{$j} == '1' and $out[] = $in[$j];
}
count($out) >= $minLength && count($out) <= $max and $return[] = $out;
}
return $return;
}
?>
output
Array
(
[0] => test_1
[1] => test_2
[2] => test_3
[3] => test_1,test_2
[4] => test_1,test_3
[5] => test_2,test_3
[6] => test_1,test_2,test_3
)
The Math_Combinatorics in PEAR repository does exactly what you want:
A package that returns all the combinations and permutations, without
repetition, of a given set and subset size. Associative arrays are
preserved.
require_once 'Math/Combinatorics.php';
$combinatorics = new Math_Combinatorics;
$input = array(1, 2, 3, 4, 5, 6, 7);
$output = $combinatorics->combinations($input, 5); // 5 is the subset size
// 1,2,3,4,5
// 1,2,3,4,6
// 1,2,3,4,7
// 1,2,3,5,6
// 1,2,3,5,7
// 1,2,3,6,7
// 1,2,4,5,6
// 1,2,4,5,7
// 1,2,4,6,7
// 1,2,5,6,7
// 1,3,4,5,6
// 1,3,4,5,7
// 1,3,4,6,7
// 1,3,5,6,7
// 1,4,5,6,7
// 2,3,4,5,6
// 2,3,4,5,7
// 2,3,4,6,7
// 2,3,5,6,7
// 2,4,5,6,7
// 3,4,5,6,7
Another solution that bases on stack. It's quit fast but eats much memory.
Hope that helps someone.
In detail:
function _combine($numbers, $length)
{
$combinations = array();
$stack = array();
// every combinations can be ordered
sort($numbers);
// startup
array_push($stack, array(
'store' => array(),
'options' => $numbers,
));
while (true) {
// pop a item
$item = array_pop($stack);
// end of stack
if (!$item) {
break;
}
// valid store
if ($length <= count($item['store'])) {
$combinations[] = $item['store'];
continue;
}
// bypass when options are not enough
if (count($item['store']) + count($item['options']) < $length) {
continue;
}
foreach ($item['options'] as $index => $n) {
$newStore = $item['store'];
$newStore[] = $n;
// every combine can be ordered
// so accept only options which is greater than store numbers
$newOptions = array_slice($item['options'], $index + 1);
// push new items
array_push($stack, array(
'store' => $newStore,
'options' => $newOptions,
));
}
}
return $combinations;
}
Improved this answer to work with associative array as well:
function uniqueCombination($values, $minLength = 1, $maxLength = 2000) {
$count = count($values);
$size = pow(2, $count);
$keys = array_keys($values);
$return = [];
for($i = 0; $i < $size; $i ++) {
$b = sprintf("%0" . $count . "b", $i);
$out = [];
for($j = 0; $j < $count; $j ++) {
if ($b[$j] == '1') {
$out[$keys[$j]] = $values[$keys[$j]];
}
}
if (count($out) >= $minLength && count($out) <= $maxLength) {
$return[] = $out;
}
}
return $return;
}
Eg:
print_r(uniqueCombination([
'a' => 'xyz',
'b' => 'pqr',
]);
Result:
Array
(
[0] => Array
(
[b] => pqr
)
[1] => Array
(
[a] => xyz
)
[2] => Array
(
[a] => xyz
[b] => pqr
)
)
It will still work for non-associative arrays:
print_r(uniqueCombination(['a', 'b']);
Result:
Array
(
[0] => Array
(
[1] => b
)
[1] => Array
(
[0] => a
)
[2] => Array
(
[0] => a
[1] => b
)
)
New solution which optimizes speed and memory for combining algorithm
Mindset: generate combinations K numbers of Array of numbers. New solution will use K 'for' statements. One 'for' One number.
Such as: $K = 5 mean that 5 of 'for' statements is used
$total = count($array);
$i0 = -1;
for ($i1 = $i0 + 1; $i1 < $total; $i1++) {
for ($i2 = $i1 + 1; $i2 < $total; $i2++) {
for ($i3 = $i2 + 1; $i3 < $total; $i3++) {
for ($i4 = $i3 + 1; $i4 < $total; $i4++) {
for ($i5 = $i4 + 1; $i5 < $total; $i5++) {
$record = array();
for ($i = 1; $i <= $k; $i++) {
$t = "i$i";
$record[] = $array[$$t];
}
$callback($record);
}
}
}
}
}
And detail of code that generated the real code that will be execute by eval() function
function combine($array, $k, $callback)
{
$total = count($array);
$init = '
$i0 = -1;
';
$sample = '
for($i{current} = $i{previous} + 1; $i{current} < $total; $i{current}++ ) {
{body}
}
';
$do = '
$record = array();
for ($i = 1; $i <= $k; $i++) {
$t = "i$i";
$record[] = $array[$$t];
}
$callback($record);
';
$for = '';
for ($i = $k; $i >= 1; $i--) {
switch ($i) {
case $k:
$for = str_replace(['{current}', '{previous}', '{body}'], [$i, $i - 1, $do], $sample);
break;
case 1:
$for = $init . str_replace(['{current}', '{previous}', '{body}'], [$i, $i - 1, $for], $sample);
break;
default:
$for = str_replace(['{current}', '{previous}', '{body}'], [$i, $i - 1, $for], $sample);
break;
}
}
// execute
eval($for);
}
How to combine K numbers of Array
$k = 5;
$array = array(1, 2, 3, 4, 5, 6, 7);
$callback = function ($record) {
echo implode($record) . "\n";
};
combine($array, $k, $callback);
I found the other answers here confusing or overly complicated, so I wrote my own. I think this is a simple solution with a recursive method. The basic idea is that you go through your array and for each item decide whether or not it is in the combination (actually, you don't decide, you recursively try both ways). You make this choice for the first item and then combine it with the recursively generated combinations of the rest of the array. This solution fills a result array with every combination of your array as a sub-array. It uses the items in order and it preserves associations, including with numeric keys.
function combinations(array $items, int $numToChoose, array &$results, $comb = []): void {
if (count($items) < $numToChoose) {
throw new \Exception("Asked to choose $numToChoose items from an array of length ". count($items));
}
// if nothing left to choose, we have a complete combination
if ($numToChoose === 0) {
$results[] = $comb;
return;
}
// if we have to choose everything at this point, then we know what to do
if (count($items) == $numToChoose) {
$results[] = $comb + $items;
return;
}
// The recursive cases: either use the first element or not and find combinations of the rest
$val = reset($items);
$key = key($items);
unset($items[$key]);
// not using it
combinations($items, $numToChoose, $results, $comb);
// using it
$comb[$key] = $val;
combinations($items, $numToChoose - 1, $results, $comb);
}
// Do a test run
$combs = [];
combinations([1=>1, 2=>2, 3=>3], 2, $combs);
var_dump($perms);
This results in the output:
array(3) {
[0]=>
array(2) {
[2]=>
int(2)
[3]=>
int(3)
}
[1]=>
array(2) {
[1]=>
int(1)
[3]=>
int(3)
}
[2]=>
array(2) {
[1]=>
int(1)
[2]=>
int(2)
}
}
I needed a combining function that included subsets, so I took #Nguyen Van Vinh's answer and modified it for my needs.
If you pass [1,2,3,4] to the function, it returns every unique combination and subset, sorted:
[
[1,2,3,4], [1,2,3], [1,2,4], [1,3,4], [2,3,4], [1,2], [1,3], [1,4], [2,3], [2,4], [3,4], [1], [2], [3], [4]
]
Here's the function:
function get_combinations_with_length( $numbers, $length ){
$result = array();
$stack = array();
// every combinations can be ordered
sort($numbers);
// startup
array_push($stack, array(
'store' => array(),
'options' => $numbers,
));
while (true) {
// pop a item
$item = array_pop($stack);
// end of stack
if (!$item) break;
// valid store
if ($length <= count($item['store'])) {
$result[] = $item['store'];
continue;
}
// bypass when options are not enough
if (count($item['store']) + count($item['options']) < $length) {
continue;
}
foreach ($item['options'] as $i=>$n) {
$newStore = $item['store'];
$newStore[] = $n;
// every combine can be ordered, so accept only options that are greater than store numbers
$newOptions = array_slice($item['options'], $i + 1);
// array_unshift to sort numerically, array_push to reverse
array_unshift($stack, array(
'store' => $newStore,
'options' => $newOptions,
));
}
}
return $result;
}
function get_all_combinations( $numbers ){
$length = count($numbers);
$result = [];
while ($length > 0) {
$result = array_merge($result, get_combinations_with_length( $numbers, $length ));
$length--;
}
return $result;
}
$numbers = [1,2,3,4];
$result = get_all_combinations($numbers);
echo 'START: '.json_encode( $numbers ).'<br><br>';
echo 'RESULT: '.json_encode( $result ).'<br><br>';
echo '('.count($result).' combination subsets found)';
I know that array_chunk() allows to split an array into several chunks, but the number of chunks changes according to the number of elements. What I need is to always split the array into a specific number of arrays like 4 arrays for example.
The following code splits the array into 3 chunks, two chunks with 2 elements each and 1 chunk with 1 element. What I would like is to split the array always into 4 chunks, no matter the number of total elements that the array has, but always trying to divide the elements evenly in the chunks like the array_chunck function does. How can I accomplish this? Is there any PHP function for this?
$input_array = array('a', 'b', 'c', 'd', 'e');
print_r(array_chunk($input_array, 2));
print_r(array_chunk($input_array, 2, true));
Thank you.
You can try
$input_array = array(
'a',
'b',
'c',
'd',
'e'
);
print_r(partition($input_array, 4));
Output
Array
(
[0] => Array
(
[0] => a
[1] => b
)
[1] => Array
(
[0] => c
)
[2] => Array
(
[0] => d
)
[3] => Array
(
[0] => e
)
)
Function Used
/**
*
* #param Array $list
* #param int $p
* #return multitype:multitype:
* #link http://www.php.net/manual/en/function.array-chunk.php#75022
*/
function partition(Array $list, $p) {
$listlen = count($list);
$partlen = floor($listlen / $p);
$partrem = $listlen % $p;
$partition = array();
$mark = 0;
for($px = 0; $px < $p; $px ++) {
$incr = ($px < $partrem) ? $partlen + 1 : $partlen;
$partition[$px] = array_slice($list, $mark, $incr);
$mark += $incr;
}
return $partition;
}
So many complex answers here. I'll post my simple solution :)
function splitMyArray(array $input_array, int $size, $preserve_keys = null): array
{
$nr = (int)ceil(count($input_array) / $size);
if ($nr > 0) {
return array_chunk($input_array, $nr, $preserve_keys);
}
return $input_array;
}
Usage:
$newArray = splitMyArray($my_array, 3);
More details here: https://zerowp.com/split-php-array-in-x-equal-number-of-elements/
Divide the size of the array with the number of chunks you want and supply that as the size of each chunk.
function array_chunks_fixed($input_array, $chunks=3 /*Default chunk size 3*/) {
if (sizeof($input_array) > 0) {
return array_chunk($input_array, intval(ceil(sizeof($input_array) / $chunks)));
}
return array();
}
And call it like this:
array_chunks_fixed($myarray, 2); //override the default number of '3'
This what i write and work well
print_r(array_divide($input,3));
function array_divide($array, $segmentCount) {
$dataCount = count($array);
if ($dataCount == 0) return false;
$segmentLimit = 1;
//if($segmentCount > $segmentLimit)
// $segmentLimit = $segmentCount;
$outputArray = array();
$i = 0;
while($dataCount >= $segmentLimit) {
if( $segmentCount == $i)
$i = 0;
if(!array_key_exists($i, $outputArray))
$outputArray[$i] = array();
$outputArray[$i][] = array_splice($array,0,$segmentLimit)[0] ;
$dataCount = count($array);
$i++;
}
if($dataCount > 0) $outputArray[] = $array;
return $outputArray;
}
Another implementation similar to #Baba's partition() function.
// http://php.net/manual/en/function.array-slice.php#94138
// split the given array into n number of pieces
function array_split($array, $pieces=2)
{
if ($pieces < 2)
return array($array);
$newCount = ceil(count($array)/$pieces);
$a = array_slice($array, 0, $newCount);
$b = array_split(array_slice($array, $newCount), $pieces-1);
return array_merge(array($a),$b);
}
// Examples:
$a = array(1,2,3,4,5,6,7,8,9,10);
array_split($a, 2); // array(array(1,2,3,4,5), array(6,7,8,9,10))
array_split($a, 3); // array(array(1,2,3,4), array(5,6,7), array(8,9,10))
array_split($a, 4); // array(array(1,2,3), array(4,5,6), array(7,8), array(9,10))
This should work:
function getChunks(array $array, $chunks)
{
if (count($array) < $chunks)
{
return array_chunk($array, 1);
}
$new_array = array();
for ($i = 0, $n = floor(count($array) / $chunks); $i < $chunks; ++$i)
{
$slice = $i == $chunks - 1 ? array_slice($array, $i * $n) : array_slice($array, $i * $n, $n);
$new_array[] = $slice;
}
return $new_array;
}
$input_array = array('a', 'b', 'c', 'd', 'e');
echo '<pre>' . print_r(getChunks($input_array, 4), TRUE) . '</pre>';
If someone is looking for a solution to divide the super array into smaller number of separate arrays.
$count = count($appArray); //$appArray contains all elements
$repoCount = 3; //No. of parts to divide
$n = floor($count/$repoCount);
$rem = $count % $repoCount;
$j=1;
while($j <= $repoCount){
${"arr_" . $j} = array();
$j++;
}
$j=0;
$k=1;
for($i=0; $i < $count; $i++){
if($j < $n){
array_push(${"arr_" . $k}, $appArray[$i]);
$j++;
}
else if($k < $repoCount){
$j=0;
$k++;
--$i;
}
if($i >= ($count-$rem)){
$k=1;
for($j=0; $j < $rem; $j++, $i++, $k++){
array_push(${"arr_" . $k},$appArray[$i]);
}
break;
}
}
Here is my code to get all possibilities:
$seq[1] = 'd';
$seq[2] = 'f';
$seq[3] = 'w';
$seq[4] = 's';
for($i = 1; $i < 5; $i++)
{
$s['length_1'][] = $seq[$i];
$c1++;
for($i2 = $i+1; $i2 < 5; $i2++)
{
$s['length_2'][] = $seq[$i].$seq[$i2];
$last = $seq[$i].$seq[$i2];
$c2++;
for($i3 = $i2+1; $i3 < 5; $i3++)
{
$s['length_3'][] = $last.$seq[$i3];
$last = $last.$seq[$i3];
$c3++;
for($i4 = $i3+1; $i4 < 5; $i4++)
{
$s['length_4'][] = $last.$seq[$i4];
$c4++;
}
}
}
}
for($i = 0; $i < $c1; $i++)
echo $s['length_1'][$i].'<br>';
for($i = 0; $i < $c2; $i++)
echo $s['length_2'][$i].'<br>';
for($i = 0; $i < $c3; $i++)
echo $s['length_3'][$i].'<br>';
for($i = 0; $i < $c4; $i++)
echo $s['length_4'][$i].'<br>';
But if I want to add more, then I will have to add one more loop. So, how can I do it with recursion? I try, I try, but I really can't do it.
Please help and post example as simple as possible.
Thank you.
One algorithm is here,
function getCombinations($base,$n){
$baselen = count($base);
if($baselen == 0){
return;
}
if($n == 1){
$return = array();
foreach($base as $b){
$return[] = array($b);
}
return $return;
}else{
//get one level lower combinations
$oneLevelLower = getCombinations($base,$n-1);
//for every one level lower combinations add one element to them that the last element of a combination is preceeded by the element which follows it in base array if there is none, does not add
$newCombs = array();
foreach($oneLevelLower as $oll){
$lastEl = $oll[$n-2];
$found = false;
foreach($base as $key => $b){
if($b == $lastEl){
$found = true;
continue;
//last element found
}
if($found == true){
//add to combinations with last element
if($key < $baselen){
$tmp = $oll;
$newCombination = array_slice($tmp,0);
$newCombination[]=$b;
$newCombs[] = array_slice($newCombination,0);
}
}
}
}
}
return $newCombs;
}
I know it is not efficent in any way, but using in small sets should not be a problem
first base parameter is an array containing elements to be considered when generating combinations.
for simple usage and output:
var_dump(getCombinations(array("a","b","c","d"),2));
and output is
array
0 =>
array
0 => string 'a' (length=1)
1 => string 'b' (length=1)
1 =>
array
0 => string 'a' (length=1)
1 => string 'c' (length=1)
2 =>
array
0 => string 'a' (length=1)
1 => string 'd' (length=1)
3 =>
array
0 => string 'b' (length=1)
1 => string 'c' (length=1)
4 =>
array
0 => string 'b' (length=1)
1 => string 'd' (length=1)
5 =>
array
0 => string 'c' (length=1)
1 => string 'd' (length=1)
To list all subsets of an array, using this combinations algorithm just execute
$base =array("a","b","c","d");
for($i = 1; $i<=4 ;$i++){
$comb = getCombinations($base,$i);
foreach($comb as $c){
echo implode(",",$c)."<br />";
}
}
And output is
a
b
c
d
a,b
a,c
a,d
b,c
b,d
c,d
a,b,c
a,b,d
a,c,d
b,c,d
a,b,c,d
Here's a simple algo. Iterate from 1 to 2count(array)-1. On each iteration, if j-th bit in a binary representation of the loop counter is equal to 1, include j-th element in a combination.
As PHP needs to be able to calculate 2count(array) as an integer, this may never exceed PHP_INT_MAX. On a 64-bit PHP installation your array cannot have more than 62 elements, as 262 stays below PHP_INT_MAX while 263 exceeds it.
EDIT: This computes all possible combinations, not permutations (ie, 'abc' = 'cba'). It does so by representing the original array in binary and "counting up" from 0 to the binary representation of the full array, effectively building a list of every possible unique combination.
$a = array('a', 'b', 'c', 'd');
$len = count($a);
$list = array();
for($i = 1; $i < (1 << $len); $i++) {
$c = '';
for($j = 0; $j < $len; $j++)
if($i & (1 << $j))
$c .= $a[$j];
$list[] = $c;
}
print_r($list);
Here it is:
<?php
function combinations($text,$space)
{
// $text is a variable which will contain all the characters/words of which we want to make all the possible combinations
// Let's make an array which will contain all the characters
$characters=explode(",", $text);
$x=count($characters);
$comb = fact($x);
// In this loop we will be creating all the possible combinations of the positions that are there in the array $characters
for ($y=1; $y<= $comb; $y++)
{
$ken = $y-1;
$f = 1;
$a = array();
for($iaz=1; $iaz<=$x; $iaz++)
{
$a[$iaz] = $iaz;
$f = $f*$iaz;
}
for($iaz=1; $iaz<=$x-1; $iaz++)
{
$f = $f/($x+1-$iaz);
$selnum = $iaz+$ken/$f;
$temp = $a[$selnum];
for($jin=$selnum; $jin>=$iaz+1; $jin--)
{
$a[$jin] = $a[$jin-1];
}
$a[$iaz] = $temp;
$ken = $ken%$f;
}
$t=1;
// Let’s start creating a word combination: we have all the necessary positions
$newtext="";
// Here is the while loop that creates the word combination
while ($t<=$x)
{
$newtext.=$characters[$a[$t]-1]."$space";
$t++;
}
$combinations[] = $newtext ;
}
return $combinations;
}
function fact($a){
if ($a==0) return 1;
else return $fact = $a * fact($a-1);
}
$a = combinations("d,f,w,s","");
foreach ($a as $v) {
echo "$v"."\n";
}
?>
Output:
dfws
dfsw
dwfs
dwsf
dsfw
dswf
fdws
fdsw
fwds
fwsd
fsdw
fswd
wdfs
wdsf
wfds
wfsd
wsdf
wsfd
sdfw
sdwf
sfdw
sfwd
swdf
swfd
Also, read this;
You can do this:
function combinations($arr) {
$combinations = array_fill(0, count($arr)+1, array());
$combinations[0] = array('');
for ($i = 0, $n = count($arr); $i < $n; ++$i) {
for ($l = $n-$i; $l > 0; --$l) {
$combinations[$l][] = implode('', array_slice($arr, $i, $l));
}
}
return $combinations;
}
Here’s an example:
$arr = array('d', 'f', 'w', 's');
var_dump(combinations($arr));
This produces the following array:
array(
array(''), // length=0
array('d', 'f', 'w', 's'), // length=1
array('df', 'fw', 'ws'), // length=2
array('dfw', 'fws'), // length=3
array('dfws') // length=4
)
A brief explanation:
For each i with 0 ≤ i < n, get all sub-arrays arr[i,i+l] with each possible length of 0 < l ≤ n - i.
Here is my function to print all possible character combinations:
function printCombinations($var, $begin = 0, $preText = "") {
for($i = $begin; $i < count($var); $i++) {
echo $preText . $var[$i] . "\n";
if(($i+1) < count($var))
printCombinations($var, $i+1, $preText . $var[$i]);
}
}
printCombinations(array('a','b','c','d','e'));
here is another way to do it in codeigniter/php.
`function recursiveCombinations($var,$n = '') {
$len = count($var);
if ($n == 0){
return array(array());
}
$arr = [];
for ($i = 0;$i<$len;$i++){
$m = $var[$i];
$remLst = array_slice($var, $i + 1);
$remainlst_combo = $this->recursiveCombinations($remLst, $n-1);
foreach ($remainlst_combo as $key => $val){
array_push($arr,array_merge(array($m),$val));
}
}
return $arr;
}
$arr = ['a','b','c','d','e','f','g','h','i','j','l','m','n','o','p'];
$n = $this->recursiveCombinations($arr,5);`
I have an array of 7 numbers (1,2,3,4,5,6,7) and I want to choose 5 of the numbers like
(1,2,3,4,5), (1,2,3,4,6), (1,2,3,4,7).
Note that (1,2,3,4,5) is equal to (4,5,3,1,2), so only one of those should be included in the output.
I would like to know if there is a function in PHP or any algorithm that can do this ?
I have no idea where to start from.
Can you help me ?
I want all the combinations of 7 given numbers ( they are taken from an array ) put into 5 slots, disregarding order.
You can use the solution found here http://stereofrog.com/blok/on/070910.
Incase the link goes down here's the code....
class Combinations implements Iterator
{
protected $c = null;
protected $s = null;
protected $n = 0;
protected $k = 0;
protected $pos = 0;
function __construct($s, $k) {
if(is_array($s)) {
$this->s = array_values($s);
$this->n = count($this->s);
} else {
$this->s = (string) $s;
$this->n = strlen($this->s);
}
$this->k = $k;
$this->rewind();
}
function key() {
return $this->pos;
}
function current() {
$r = array();
for($i = 0; $i < $this->k; $i++)
$r[] = $this->s[$this->c[$i]];
return is_array($this->s) ? $r : implode('', $r);
}
function next() {
if($this->_next())
$this->pos++;
else
$this->pos = -1;
}
function rewind() {
$this->c = range(0, $this->k);
$this->pos = 0;
}
function valid() {
return $this->pos >= 0;
}
protected function _next() {
$i = $this->k - 1;
while ($i >= 0 && $this->c[$i] == $this->n - $this->k + $i)
$i--;
if($i < 0)
return false;
$this->c[$i]++;
while($i++ < $this->k - 1)
$this->c[$i] = $this->c[$i - 1] + 1;
return true;
}
}
foreach(new Combinations("1234567", 5) as $substring)
echo $substring, ' ';
12345 12346 12347 12356 12357 12367 12456 12457 12467 12567 13456 13457 13467 13567 14567 23456 23457 23467 23567 24567 34567
<?php
echo "<pre>";
$test = array("test_1","test_2","test_3");
// Get Combination
$return = uniqueCombination($test);
//Sort
sort($return);
//Pretty Print
print_r(array_map(function($v){ return implode(",", $v); }, $return));
function uniqueCombination($in, $minLength = 1, $max = 2000) {
$count = count($in);
$members = pow(2, $count);
$return = array();
for($i = 0; $i < $members; $i ++) {
$b = sprintf("%0" . $count . "b", $i);
$out = array();
for($j = 0; $j < $count; $j ++) {
$b{$j} == '1' and $out[] = $in[$j];
}
count($out) >= $minLength && count($out) <= $max and $return[] = $out;
}
return $return;
}
?>
output
Array
(
[0] => test_1
[1] => test_2
[2] => test_3
[3] => test_1,test_2
[4] => test_1,test_3
[5] => test_2,test_3
[6] => test_1,test_2,test_3
)
The Math_Combinatorics in PEAR repository does exactly what you want:
A package that returns all the combinations and permutations, without
repetition, of a given set and subset size. Associative arrays are
preserved.
require_once 'Math/Combinatorics.php';
$combinatorics = new Math_Combinatorics;
$input = array(1, 2, 3, 4, 5, 6, 7);
$output = $combinatorics->combinations($input, 5); // 5 is the subset size
// 1,2,3,4,5
// 1,2,3,4,6
// 1,2,3,4,7
// 1,2,3,5,6
// 1,2,3,5,7
// 1,2,3,6,7
// 1,2,4,5,6
// 1,2,4,5,7
// 1,2,4,6,7
// 1,2,5,6,7
// 1,3,4,5,6
// 1,3,4,5,7
// 1,3,4,6,7
// 1,3,5,6,7
// 1,4,5,6,7
// 2,3,4,5,6
// 2,3,4,5,7
// 2,3,4,6,7
// 2,3,5,6,7
// 2,4,5,6,7
// 3,4,5,6,7
Another solution that bases on stack. It's quit fast but eats much memory.
Hope that helps someone.
In detail:
function _combine($numbers, $length)
{
$combinations = array();
$stack = array();
// every combinations can be ordered
sort($numbers);
// startup
array_push($stack, array(
'store' => array(),
'options' => $numbers,
));
while (true) {
// pop a item
$item = array_pop($stack);
// end of stack
if (!$item) {
break;
}
// valid store
if ($length <= count($item['store'])) {
$combinations[] = $item['store'];
continue;
}
// bypass when options are not enough
if (count($item['store']) + count($item['options']) < $length) {
continue;
}
foreach ($item['options'] as $index => $n) {
$newStore = $item['store'];
$newStore[] = $n;
// every combine can be ordered
// so accept only options which is greater than store numbers
$newOptions = array_slice($item['options'], $index + 1);
// push new items
array_push($stack, array(
'store' => $newStore,
'options' => $newOptions,
));
}
}
return $combinations;
}
Improved this answer to work with associative array as well:
function uniqueCombination($values, $minLength = 1, $maxLength = 2000) {
$count = count($values);
$size = pow(2, $count);
$keys = array_keys($values);
$return = [];
for($i = 0; $i < $size; $i ++) {
$b = sprintf("%0" . $count . "b", $i);
$out = [];
for($j = 0; $j < $count; $j ++) {
if ($b[$j] == '1') {
$out[$keys[$j]] = $values[$keys[$j]];
}
}
if (count($out) >= $minLength && count($out) <= $maxLength) {
$return[] = $out;
}
}
return $return;
}
Eg:
print_r(uniqueCombination([
'a' => 'xyz',
'b' => 'pqr',
]);
Result:
Array
(
[0] => Array
(
[b] => pqr
)
[1] => Array
(
[a] => xyz
)
[2] => Array
(
[a] => xyz
[b] => pqr
)
)
It will still work for non-associative arrays:
print_r(uniqueCombination(['a', 'b']);
Result:
Array
(
[0] => Array
(
[1] => b
)
[1] => Array
(
[0] => a
)
[2] => Array
(
[0] => a
[1] => b
)
)
New solution which optimizes speed and memory for combining algorithm
Mindset: generate combinations K numbers of Array of numbers. New solution will use K 'for' statements. One 'for' One number.
Such as: $K = 5 mean that 5 of 'for' statements is used
$total = count($array);
$i0 = -1;
for ($i1 = $i0 + 1; $i1 < $total; $i1++) {
for ($i2 = $i1 + 1; $i2 < $total; $i2++) {
for ($i3 = $i2 + 1; $i3 < $total; $i3++) {
for ($i4 = $i3 + 1; $i4 < $total; $i4++) {
for ($i5 = $i4 + 1; $i5 < $total; $i5++) {
$record = array();
for ($i = 1; $i <= $k; $i++) {
$t = "i$i";
$record[] = $array[$$t];
}
$callback($record);
}
}
}
}
}
And detail of code that generated the real code that will be execute by eval() function
function combine($array, $k, $callback)
{
$total = count($array);
$init = '
$i0 = -1;
';
$sample = '
for($i{current} = $i{previous} + 1; $i{current} < $total; $i{current}++ ) {
{body}
}
';
$do = '
$record = array();
for ($i = 1; $i <= $k; $i++) {
$t = "i$i";
$record[] = $array[$$t];
}
$callback($record);
';
$for = '';
for ($i = $k; $i >= 1; $i--) {
switch ($i) {
case $k:
$for = str_replace(['{current}', '{previous}', '{body}'], [$i, $i - 1, $do], $sample);
break;
case 1:
$for = $init . str_replace(['{current}', '{previous}', '{body}'], [$i, $i - 1, $for], $sample);
break;
default:
$for = str_replace(['{current}', '{previous}', '{body}'], [$i, $i - 1, $for], $sample);
break;
}
}
// execute
eval($for);
}
How to combine K numbers of Array
$k = 5;
$array = array(1, 2, 3, 4, 5, 6, 7);
$callback = function ($record) {
echo implode($record) . "\n";
};
combine($array, $k, $callback);
I found the other answers here confusing or overly complicated, so I wrote my own. I think this is a simple solution with a recursive method. The basic idea is that you go through your array and for each item decide whether or not it is in the combination (actually, you don't decide, you recursively try both ways). You make this choice for the first item and then combine it with the recursively generated combinations of the rest of the array. This solution fills a result array with every combination of your array as a sub-array. It uses the items in order and it preserves associations, including with numeric keys.
function combinations(array $items, int $numToChoose, array &$results, $comb = []): void {
if (count($items) < $numToChoose) {
throw new \Exception("Asked to choose $numToChoose items from an array of length ". count($items));
}
// if nothing left to choose, we have a complete combination
if ($numToChoose === 0) {
$results[] = $comb;
return;
}
// if we have to choose everything at this point, then we know what to do
if (count($items) == $numToChoose) {
$results[] = $comb + $items;
return;
}
// The recursive cases: either use the first element or not and find combinations of the rest
$val = reset($items);
$key = key($items);
unset($items[$key]);
// not using it
combinations($items, $numToChoose, $results, $comb);
// using it
$comb[$key] = $val;
combinations($items, $numToChoose - 1, $results, $comb);
}
// Do a test run
$combs = [];
combinations([1=>1, 2=>2, 3=>3], 2, $combs);
var_dump($perms);
This results in the output:
array(3) {
[0]=>
array(2) {
[2]=>
int(2)
[3]=>
int(3)
}
[1]=>
array(2) {
[1]=>
int(1)
[3]=>
int(3)
}
[2]=>
array(2) {
[1]=>
int(1)
[2]=>
int(2)
}
}
I needed a combining function that included subsets, so I took #Nguyen Van Vinh's answer and modified it for my needs.
If you pass [1,2,3,4] to the function, it returns every unique combination and subset, sorted:
[
[1,2,3,4], [1,2,3], [1,2,4], [1,3,4], [2,3,4], [1,2], [1,3], [1,4], [2,3], [2,4], [3,4], [1], [2], [3], [4]
]
Here's the function:
function get_combinations_with_length( $numbers, $length ){
$result = array();
$stack = array();
// every combinations can be ordered
sort($numbers);
// startup
array_push($stack, array(
'store' => array(),
'options' => $numbers,
));
while (true) {
// pop a item
$item = array_pop($stack);
// end of stack
if (!$item) break;
// valid store
if ($length <= count($item['store'])) {
$result[] = $item['store'];
continue;
}
// bypass when options are not enough
if (count($item['store']) + count($item['options']) < $length) {
continue;
}
foreach ($item['options'] as $i=>$n) {
$newStore = $item['store'];
$newStore[] = $n;
// every combine can be ordered, so accept only options that are greater than store numbers
$newOptions = array_slice($item['options'], $i + 1);
// array_unshift to sort numerically, array_push to reverse
array_unshift($stack, array(
'store' => $newStore,
'options' => $newOptions,
));
}
}
return $result;
}
function get_all_combinations( $numbers ){
$length = count($numbers);
$result = [];
while ($length > 0) {
$result = array_merge($result, get_combinations_with_length( $numbers, $length ));
$length--;
}
return $result;
}
$numbers = [1,2,3,4];
$result = get_all_combinations($numbers);
echo 'START: '.json_encode( $numbers ).'<br><br>';
echo 'RESULT: '.json_encode( $result ).'<br><br>';
echo '('.count($result).' combination subsets found)';