I have a four-level multidimensional array. I need to sort in ascending order (ASC) the numeric "leaves" in order to calculate the median of the values.
I tried array_walk_recursive(), array_multisort(), usort(), etc. but was unable to find a working solution.
Here's a schematic of the array:
(
[2017-05-01] => Array
(
[DC] => Array
(
[IT] => Array
(
[0] => 90
[1] => 0
)
[DE] => Array
(
[0] => 18
[1] => 315
[2] => 40
[3] =>
[4] => 69
)
[Other] => Array
(
[0] => 107
[1] => 46
[2] =>
[3] =>
[4] => 27
[5] => 22
)
)
)
)
This will output the deepest subarrays' median values using the input array's structure.
I'm including hard-casting of median values (one or both in a subset) as integers in the event that the value(s) are empty strings. I'll also assume that you will want 0 as the output if a subset is empty.
Code: (Demo)
$array=[
'2017-05-01'=>[
'DC'=>[
'IT'=>[90, 0],
'DE'=>[18, 315, 40, '', 69, 211],
'Other'=>[107, 46, '', '', 27, 22]
]
],
'2017-05-02'=>[
'DC'=>[
'IT'=>[70, 40, 55],
'DE'=>['', 31, 4, '', 9],
'Other'=>[1107, 12, 0, 20, 1, 11, 21]
]
],
'fringe case'=>[
'DC'=>[
'IT'=>[],
'DE'=>['', '', '', 99],
'Other'=>['', 99]
]
]
];
foreach ($array as $k1 => $lv1) {
foreach ($lv1 as $k2 => $lv2) {
foreach ($lv2 as $k3 => $lv3) {
sort($lv3); // order values ASC
$count = sizeof($lv3); // count number of values
$index = floor($count / 2); // get middle index or upper of middle two
if (!$count) { // count is zero
$medians[$k1][$k2][$k3] = 0;
} elseif ($count & 1) { // count is odd
$medians[$k1][$k2][$k3] = (int)$lv3[$index]; // single median
} else { // count is even
$medians[$k1][$k2][$k3] = ((int)$lv3[$index-1] + (int)$lv3[$index]) / 2; // dual median
}
}
}
}
var_export($medians);
Output:
array (
'2017-05-01' =>
array (
'DC' =>
array (
'IT' => 45,
'DE' => 54.5,
'Other' => 24.5,
),
),
'2017-05-02' =>
array (
'DC' =>
array (
'IT' => 55,
'DE' => 4,
'Other' => 12,
),
),
'fringe case' =>
array (
'DC' =>
array (
'IT' => 0,
'DE' => 0,
'Other' => 49.5,
),
),
)
*for the record, $count & 1 is a bitwise comparison that determines if the value is odd without performing arithmetic (and is the most efficient way of performing this check within php).
*also, if you wanted to simply overwrite the values of the input array, you could modify by reference by writing & before $lv1, $lv2, and $lv3 in the foreach declarations then save the median value to $lv3. Demo The benefit in doing so removes key declarations and making your code more brief.
As it turns out, there is a way to do what the OP seeks using a combination of usort() and array_walk(), each of which takes a callback, as follows:
<?php
// median code:
//http://www.mdj.us/web-development/php-programming/calculating-the-median-average-values-of-an-array-with-php/
function calculate_median($arr) {
sort($arr);
$count = count($arr); //total numbers in array
$middleval = floor(($count-1)/2); // find the middle value, or the lowest middle value
if($count % 2) { // odd number, middle is the median
$median = $arr[$middleval];
} else { // even number, calculate avg of 2 medians
$low = $arr[$middleval];
$high = $arr[$middleval+1];
$median = (($low+$high)/2);
}
return $median;
}
$a = [];
$a["2017-05-01"] = ["DC"];
$a["2017-05-01"]["DC"]["IT"] = [90,0];
$a["2017-05-01"]["DC"]["DE"] = [18,315,40,"",69];
$a["2017-05-01"]["DC"]["Other"] = [107,46,"","",27,22];
function sort_by_order ($a, $b)
{
if ($a == "") $a = 0;
if ($b == "") $b = 0;
return $a - $b;
}
function test($item,$key){
echo $key," ";
if (is_array($item)) {
echo array_keys($item)[1],"\n";
$popped = array_pop($item);
foreach ($popped as $key => $arr) {
usort($arr, 'sort_by_order');
echo "Median ($key): ",calculate_median( $arr ),"\n";
}
}
}
array_walk($a, 'test');
See demo here. Also, see this example based on the OP's sandbox.
Although the OP's code does not show the array keys as quoted, beware they should be in the actual code, otherwise PHP will do math with 2017-05-01 and you'll see a key of 2011. Interesting read here about usort.
The median code I extracted from here.
Interestingly, the conventional wisdom about sorting numbers to determine the median is not necessarily the only way to obtain that result. Apparently, it can also be done and perhaps more efficiently by finding a pivot number and dividing the series of numbers into three parts (see this response).
Related
I am trying to reduce an associative array to pairs of unique values, whereby the keys are letters to be paired and the values are their count()s.
Each pair cannot contain the same letter twice such as AA or BB.
It is permissible for more than one occurrence of the same pair.
e.g. AC, DC, AC, DC are all valid in the resultant array
Every time a letter is picked, its associated number decreases by one until none remain, or
any odd/leftover letters should be flagged and not ignored, e.g. Array([paired]=>Array(...) [unpaired]=>Array(...))
Sample input:
Array(
[A] => 8
[B] => 16
[C] => 15
[D] => 4
[E] => 1
)
Possible output:
[
'paired' => [
'BD', 'AE', 'BC', 'AC', 'BC', ...
],
'unpaired' => [
'C' => 4
]
]
I would prefer two unique letters to be chosen at random, but if that is too hard then the resultant array should be easily shuffle()-able
I have tried various combinations of array_reduce(), array_map(), array_walk(), array_unique() etc., even Pear's Math_Combinatorics, but all to no avail.
Here is a function that will generate the results you want. It iterates over the letters array, filtering out letters which have been completely used, until there is only one letter which is unused. The list of pairs and the unused letter are then returned in an array:
function random_pairs($letters) {
$pairs = array();
$letters = array_filter($letters);
while (count($letters) > 1) {
$keys = array_keys($letters);
shuffle($keys);
list($letter1, $letter2) = array_slice($keys, 0, 2);
$pairs[] = "$letter1$letter2";
$letters[$letter1]--;
$letters[$letter2]--;
$letters = array_filter($letters);
}
return array('pairs' => $pairs, 'unpaired' => $letters);
}
Sample usage:
$letters = array('A' => 8, 'B' => 16, 'C' => 15, 'D' => 4, 'E' => 1);
print_r(random_pairs($letters));
Sample output:
Array
(
[pairs] => Array
(
[0] => CB
[1] => CE
[2] => CD
[3] => BD
[4] => CB
[5] => DC
[6] => AB
[7] => CA
[8] => DA
[9] => BC
[10] => BA
[11] => BA
[12] => AB
[13] => BA
[14] => BC
[15] => AB
[16] => BC
[17] => CB
[18] => CB
[19] => CB
[20] => CB
)
[unpaired] => Array
(
[C] => 2
)
)
Demo on 3v4l.org
Here is a recursive technique. It uses array_rand() to grab two unique keys at a time. If you need the paired keys to be randomly arranged, then call shuffle() upon them. What I mean is, shuffle() might be omittable depending on your expectations.
An advantage in using array_rand() is that array_keys() doesn't need to be called since it returns random keys.
I have deliberately designed this snippet to unconditionally declare the unpaired subarray -- even if it is empty.
Code: (Demo)
function randomPairs(array $letters, array $result = []) {
if (count($letters) < 2) {
$result['unpaired'] = $letters;
return $result;
}
$keys = array_rand($letters, 2);
shuffle($keys);
$result['paired'][] = $keys[0] . $keys[1];
--$letters[$keys[0]];
--$letters[$keys[1]];
return randomPairs(array_filter($letters), $result);
}
Without recursion: (Demo)
function randomPairs(array $letters): array {
$result['paired'] = [];
while (count($letters) > 1) {
$keys = array_rand($letters, 2);
shuffle($keys);
$result['paired'][] = $keys[0] . $keys[1];
--$letters[$keys[0]];
--$letters[$keys[1]];
$letters = array_filter($letters);
}
$result['unpaired'] = $letters;
return $result;
}
I propose you this one :
I've just seen Nick's answer, but it do 15 minutes i'm trying xD I want to post my answer :p
Based on a "pickRandomValues" function, i made it dynamic, and you can as well choose the number of letters you want
function pickRandom(&$array = [], $count = 1) {
$valuesFound = [];
if (count($array) >= $count) {
for ($i = 0; $i < $count; $i++) {
$index = rand(0, count($array) - 1);
$tmp = array_splice($array, $index, 1);
$valuesFound[] = $tmp[0];
}
}
return $valuesFound;
}
And you body code
$tmpArr = [];
foreach ($a as $letter => $count) {
$t = [];
$tmpArr = array_merge($tmpArr, array_pad($t, $count, $letter));
}
$pairs = [];
while (count($tmpArr) >= $numberOfLetters) {
$pick = pickRandom($tmpArr, $numberOfLetters);
$pairs[] = implode($pick);
}
$finalArray = [
"paired" => $pairs,
"unpaired" => array_count_values($tmpArr)
];
So, it give something like this :
http://sandbox.onlinephpfunctions.com/code/3b143396b7267bd6029ff613746d6c047557a282
I have a large array with a bunch of integers in it, sorted from largest to smallest:
Array(
0 => 472,
1 => 388,
2 => 211,
3 => 194,
4 => 175,
...
29 => 13,
30 => 6,
31 => 3,
32 => 3,
33 => 1
)
What I need to do is split this array into separate arrays, with each array containing no more than a total sum of 'X'. So - assume that X is 1000:
Array (
0 => Array(
0 => 472,
1 => 388 // = 860 sum
),
1 => Array(
0 => 211, // would have caused first array to exceed 1000, so it is in here
1 => 194,
2 => 175
...
)
n => Array(
0 => 3,
1 => 3,
2 => 1 // remainder of 7
)
)
I have looked at array_chunk(), I have even tried iterating over the array using recursive functions - but am having trouble finding the best approach. Since some of these arrays could be somewhat large, I would like to do it in the most performance effective manner.
How would I split that array into chunks with a total sum each not exceeding X?
I checked Michel answer, His answer is not contained last elements of array because of sum of last elements is lower than max so i added another condition to foreach for checking the loop iteration is ended
new_array=[];
$tot=0;
$new_chunk=[];
$max=1000;
$original_count = count($original_array);
foreach($original_array as $key => $value){
if($tot + $value > $max){
$new_array[] = $new_chunk;
$tot = $value ;
$new_chunk = [$value];
continue;
}
$tot+= $value;
$new_chunk[] = $value;
if($key + 1 == $original_count)
$new_array[] = $new_chunk;
}
Something like this will do, I suppose.
$new_array=[];
$tot=0;
$new_chunk=[];
$max=1000;
foreach($original_array as $value){
if($tot + $value > $max){
$new_array[] = $new_chunk;
$tot = $value;
$new_chunk = [$value];
continue;
}
$tot+= $value;
$new_chunk[] = $value;
}
I'm probably [super]overthinking this. I'm trying to analyze an array with values like [1,9], [4,6] [5,5], [6,4], [9,1] and duplicate digits (I'm having a super brain fart and can't even remember the term for numbers like this) remove (the last two) so that only [1,9], [4,6] [5,5] are printed.
I was thinking that turning this array into a string and using preg_match, but I'm pretty sure this wouldn't work even if I had the correct regex.
If you have an array of pairs like this:
$x = array(
array(1,9),
array(4,6),
array(5,5),
array(6,4),
array(9,1)
);
Here is one way to get the unique pairs:
foreach ($x as $pair) {
sort($pair);
$unique_pairs[implode(',', $pair)] = $pair;
}
This uses string representations of each sorted pair as keys in a new array, so the result will have distinct values by definition.
As far as the printing them out part of your question, once you have the unique values you can loop over them and print them out in whichever format you like, for example:
foreach ($unique_pairs as $pair) { vprintf("[%d,%d]<br>", $pair); }
It looks like elements are distributed symmetrically.
We can cut the array in two halves and get only the first half with array_slice():
$array = array(
array(1,9),
array(4,6),
array(5,5),
array(6,4),
array(9,1),
);
print_r(array_slice($array, 0, ceil(count($array) / 2)));
Result:
Array(
[0] => Array(
[0] => 1
[1] => 9
)
[1] => Array(
[0] => 4
[1] => 6
)
[2] => Array(
[0] => 5
[1] => 5
)
)
Demo at Codepad.
ceil() is used to round the number up to the next highest integer if there is an even number of items in the array. Example: if there is 3 items in the array, 5 / 2 will return 2.5, we want 3 items so we use ceil(2.5) which gives 3.
Example with 3 items:
$array = array(
array(1,9),
array(5,5),
array(9,1),
);
print_r(array_slice($array, 0, ceil(count($array) / 2)));
Result:
Array(
[0] => Array(
[0] => 1
[1] => 9
)
[1] => Array(
[0] => 5
[1] => 5
)
)
Example with 4 items:
$array = array(
array(1,9),
array(7,7),
array(7,7),
array(9,1),
);
print_r(array_slice($array, 0, ceil(count($array) / 2)));
Result:
Array(
[0] => Array(
[0] => 1
[1] => 9
)
[1] => Array(
[0] => 7
[1] => 7
)
)
If I'm correct in understanding what you are trying to do, you want to remove the final 2 elements from the array?
There is a function in PHP called array_pop that removes the final element from the array.
$array = array_pop($array);
So if you run this twice, you will remove the final 2 elements from the array.
This is how I'd do it (and I hope I am not overthinking this :))
$stringArray = array();
$stringArray[] = '1,9';
$stringArray[] = '4,6';
$stringArray[] = '5,5';
$stringArray[] = '6,4';
$stringArray[] = '9,1';
foreach($stringArray as &$numString) {
$numString = explode(',', $numString);
usort($numString, function($a, $b) {return $a - $b;});
$numString = implode(',', $numString);
}
$a = array_unique($a);
print_r($a);
You basically explode every element into a subarray, sort it and then implode it back. After calling the array_unique, you're left with unique values in the array.
The output would be
Array
(
[0] => 1,9
[1] => 4,6
[2] => 5,5
)
The result you suggest treats [a,b] as equivalent to [b,a] which makes the problem a lot more complex. The code below gives the result you asked for, but without really understanding what the problem is that you are trying to fix and whether [1,9] is equivalent to [9,1] in the solution:
$a=array(array(1,9),array(4,6),...
$dup=array();
for ($i=0; $i<count($a) -1; $i++) {
for ($j=$i+1; $j<count($a); $j++) {
if (($a[$i][0]==$a[$j[0] && $a[$i][1]==$a[$j[1])
|| ($a[$i][0]==$a[$j[1] && $a[$i][1]==$a[$j[0])) {
$dup[]=$j;
}
}
}
foreach ($dup as $i) {
unset($a[$i]);
}
So I'm actually going to assume your question to have a different meaning than everyone else did. I believe what you're asking is:
How do you filter out array items where a reverse of the item has already been used?
<?php
// The example set you gave
$numberSets = [[1, 9], [4, 6], [5, 5], [6, 4], [9, 1]];
// Initialize an empty array to keep track of what we've seen
$keys = [];
// We use array filter to get rid of items we don't want
// (Notice that we use & on $keys, so that we can update the variable in the global scope)
$numberSets = array_filter($numberSets, function($set) use(&$keys) {
// Reverse the array
$set = array_reverse($set);
// Create a string of the items
$key = implode('', $set);
// Get the reverse of the numbers
$reversedKey = strrev($key);
// If the palindrome of our string was used, return false to filter
if (isset($keys[$reversedKey])) {
return false;
}
// Set the key so it's not used again
// Since $keys is being passed by reference it is updated in global scope
$keys[$key] = true;
// Return true to NOT filter this item, since it or it's reverse were not matched
return true;
});
var_dump($numberSets);
I need to divide an array into two arrays.
One array will contain all positive values (and zeros), the other all negative values.
Example array:
$ts = [7,-10,13,8,0,4,-7.2,-12,-3.7,3.5,-9.6,6.5,-1.7,-6.2,7];
Negatives result array:
[-10,-7.2,-12,-3.7,-9.6,-1.7,-6.2];
Non-negatives result array:
[7,13,8,0,4,3.5,6.5,7];
Without using any array functions..
Pretty straightforward. Just loop through the array and check if the number is less than 0, if so , push it in the negative array else push it in the positive array.
<?php
$ts=array(7,-10,13,8,4,-7.2,-12,-3.7,3.5,-9.6,6.5,-1.7,-6.2,7);
$pos_arr=array(); $neg_arr=array();
foreach($ts as $val)
{
($val<0) ? $neg_arr[]=$val : $pos_arr[]=$val;
}
print_r($pos_arr);
print_r($neg_arr);
OUTPUT :
Array
(
[0] => 7
[1] => 13
[2] => 8
[3] => 4
[4] => 3.5
[5] => 6.5
[6] => 7
)
Array
(
[0] => -10
[1] => -7.2
[2] => -12
[3] => -3.7
[4] => -9.6
[5] => -1.7
[6] => -6.2
)
You can use array_filter function,
$positive = array_filter($ts, function ($v) {
return $v > 0;
});
$negative = array_filter($ts, function ($v) {
return $v < 0;
});
Note: This will skip values with 0, or you can just change condition to >=0 in positive numbers filter to considered in positive group.
DEMO.
The most elegant is to use phps array_filter() function:
<?php
$ts = [ 7,-10,13,8,4,-7.2,-12,-3.7,3.5,-9.6,6.5,-1.7,-6.2,7 ];
print_r( array_filter( $ts, function( $val ) { return (0>$val); } ) );
print_r( array_filter( $ts, function( $val ) { return ! (0>$val); } ) );
?>
If you are still using an older php version you need some longer implementation:
<?php
$ts = array( 7,-10,13,8,4,-7.2,-12,-3.7,3.5,-9.6,6.5,-1.7,-6.2,7 );
print_r( array_filter( $ts, create_function( '$val', 'return (0>$val);' ) ) );
print_r( array_filter( $ts, create_function( '$val', 'return ! (0>$val);' ) ) );
?>
Food for thought, you could write a generic function that splits an array based on a boolean result:
// splits an array based on the return value of the given function
// - add to the first array if the result was 'true'
// - add to the second array if the result was 'false'
function array_split(array $arr, callable $fn)
{
$a = $b = [];
foreach ($arr as $key => $value) {
if ($fn($value, $key)) {
$a[$key] = $value;
} else {
$b[$key] = $value;
}
}
return [$a, $b];
}
list($positive, $negative) = array_split($ts, function($item) {
return $item >= 0;
});
print_r($positive);
print_r($negative);
Demo
Rather than declaring two arrays, I recommend declaring one array with two subarrays. You can either give the subarrays an index of 0 or 1 depending on the conditional evaluation with zero, or you can go a little farther by declaring a lookup array to replace the integer key with an expressive word.
Regardless of if you choose to create one array or two arrays, you should only make one loop over the input array. Making two loops or by calling array_filter() twice is needlessly inefficient.
Code: (Demo)
$ts = [7,-10,13,8,4,-7.2,-12,-3.7,3.5,-9.6,6.5,-1.7,-6.2,7];
const KEY_NAMES = [0 => 'negative', 1 => 'non-negatuve'];
$result = [];
foreach ($ts as $v) {
$result[KEY_NAMES[$v >= 0]][] = $v;
}
var_export($result);
Output:
array (
'non-negatuve' =>
array (
0 => 7,
1 => 13,
2 => 8,
3 => 4,
4 => 3.5,
5 => 6.5,
6 => 7,
),
'negative' =>
array (
0 => -10,
1 => -7.2,
2 => -12,
3 => -3.7,
4 => -9.6,
5 => -1.7,
6 => -6.2,
),
)
I've been looking at PHP array permutation / combination questions all day.. and still can't figure it out :/
If I have an array like:
20 //key being 0
20 //key being 1
22 //key being 2
24 //key being 3
I need combinations like:
20, 20, 22 //keys being 0 1 2
20, 20, 24 //keys being 0 1 3
20, 22, 24 //keys being 0 2 3
20, 22, 24 //keys being 1 2 3
The code I currently have gives me:
20, 22, 24
because it doesn't want to repeat 20... but that's what I need!
Here is the code I have. it is directly from Php recursion to get all possibilities of strings
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've been playing around with the ($b == $lastEl) line, with no luck
===============
Questions I've already looked at, and are not the same OR that created an out of memory error!:
How can I get all permutations in PHP without sequential duplicates?
Permutations - all possible sets of numbers
Combinations, Dispositions and Permutations in PHP
PHP array combinations
Get all permutations of a PHP array?
PHP: How to get all possible combinations of 1D array?
Select only unique array values from this array
Get all permutations of a PHP array?
PHP: How to get all possible combinations of 1D array?
Select only unique array values from this array
How can I get all permutations in PHP without sequential duplicates?
Algorithm to return all combinations of k elements from n
Find combination(s) sum of element(s) in array whose sum equal to a given number
Combinations, Dispositions and Permutations in PHP
PHP array combinations
Php recursion to get all possibilities of strings
How to return permutations of an array in PHP?
Permutations - all possible sets of numbers
Subset-sum problem in PHP with MySQL
Find unique combinations of values from arrays filtering out any duplicate pairs
Finding all the unique permutations of a string without generating duplicates
Generate all unique permutations
Subset sum for exactly k integers?
I've tried some of these algorithms with an array of 12 items, and end up running out of memory. However the algorithm that I'm currently using doesn't give me an out of memory error.... BUT.. I need those duplicates!
If you don't mind using a couple of global variables, you could do this in PHP (translated from a version in JavaScript):
<?PHP
$result = array();
$combination = array();
function combinations(array $myArray, $choose) {
global $result, $combination;
$n = count($myArray);
function inner ($start, $choose_, $arr, $n) {
global $result, $combination;
if ($choose_ == 0) array_push($result,$combination);
else for ($i = $start; $i <= $n - $choose_; ++$i) {
array_push($combination, $arr[$i]);
inner($i + 1, $choose_ - 1, $arr, $n);
array_pop($combination);
}
}
inner(0, $choose, $myArray, $n);
return $result;
}
print_r(combinations(array(20,20,22,24), 3));
?>
OUTPUT:
Array ( [0] => Array ( [0] => 20
[1] => 20
[2] => 22 )
[1] => Array ( [0] => 20
[1] => 20
[2] => 24 )
[2] => Array ( [0] => 20
[1] => 22
[2] => 24 )
[3] => Array ( [0] => 20
[1] => 22
[2] => 24 ) )
The pear package Math_Combinatorics makes this kind of problem fairly easy. It takes relatively little code, it's simple and straightforward, and it's pretty easy to read.
$ cat code/php/test.php
<?php
$input = array(20, 20, 22, 24);
require_once 'Math/Combinatorics.php';
$c = new Math_Combinatorics;
$combinations = $c->combinations($input, 3);
for ($i = 0; $i < count($combinations); $i++) {
$vals = array_values($combinations[$i]);
$s = implode($vals, ", ");
print $s . "\n";
}
?>
$ php code/php/test.php
20, 20, 22
20, 20, 24
20, 22, 24
20, 22, 24
If I had to package this as a function, I'd do something like this.
function combinations($arr, $num_at_a_time)
{
include_once 'Math/Combinatorics.php';
if (count($arr) < $num_at_a_time) {
$arr_count = count($arr);
trigger_error(
"Cannot take $arr_count elements $num_at_a_time "
."at a time.", E_USER_ERROR
);
}
$c = new Math_Combinatorics;
$combinations = $c->combinations($arr, $num_at_a_time);
$return = array();
for ($i = 0; $i < count($combinations); $i++) {
$values = array_values($combinations[$i]);
$return[$i] = $values;
}
return $return;
}
That will return an array of arrays. To get the text . . .
<?php
include_once('combinations.php');
$input = array(20, 20, 22, 24);
$output = combinations($input, 3);
foreach ($output as $row) {
print implode($row, ", ").PHP_EOL;
}
?>
20, 20, 22
20, 20, 24
20, 22, 24
20, 22, 24
Why not just use binary? At least then its simple and very easy to understand what each line of code does like this? Here's a function i wrote for myself in a project which i think is pretty neat!
function search_get_combos($array){
$bits = count($array); //bits of binary number equal to number of words in query;
//Convert decimal number to binary with set number of bits, and split into array
$dec = 1;
$binary = str_split(str_pad(decbin($dec), $bits, '0', STR_PAD_LEFT));
while($dec < pow(2, $bits)) {
//Each 'word' is linked to a bit of the binary number.
//Whenever the bit is '1' its added to the current term.
$curterm = "";
$i = 0;
while($i < ($bits)){
if($binary[$i] == 1) {
$curterm[] = $array[$i]." ";
}
$i++;
}
$terms[] = $curterm;
//Count up by 1
$dec++;
$binary = str_split(str_pad(decbin($dec), $bits, '0', STR_PAD_LEFT));
}
return $terms;
}
For your example, this outputs:
Array
(
[0] => Array
(
[0] => 24
)
[1] => Array
(
[0] => 22
)
[2] => Array
(
[0] => 22
[1] => 24
)
[3] => Array
(
[0] => 20
)
[4] => Array
(
[0] => 20
[1] => 24
)
[5] => Array
(
[0] => 20
[1] => 22
)
[6] => Array
(
[0] => 20
[1] => 22
[2] => 24
)
[7] => Array
(
[0] => 20
)
[8] => Array
(
[0] => 20
[1] => 24
)
[9] => Array
(
[0] => 20
[1] => 22
)
[10] => Array
(
[0] => 20
[1] => 22
[2] => 24
)
[11] => Array
(
[0] => 20
[1] => 20
)
[12] => Array
(
[0] => 20
[1] => 20
[2] => 24
)
[13] => Array
(
[0] => 20
[1] => 20
[2] => 22
)
[14] => Array
(
[0] => 20
[1] => 20
[2] => 22
[3] => 24
)
)
Had the same problem and found a different and bitwise, faster solution:
function bitprint($u) {
$s = array();
for ($n=0; $u; $n++, $u >>= 1){
if ($u&1){
$s [] = $n;
}
}
return $s;
}
function bitcount($u) {
for ($n=0; $u; $n++, $u = $u&($u-1));
return $n;
}
function comb($c,$n) {
$s = array();
for ($u=0; $u<1<<$n; $u++){
if (bitcount($u) == $c){
$s [] = bitprint($u);
}
}
return $s;
}
This one generates all size m combinations of the integers from 0 to n-1, so for example
m = 2, n = 3 and calling comb(2, 3) will produce:
0 1
0 2
1 2
It gives you index positions, so it's easy to point to array elements by index.
Edit: Fails with input comb(30, 5). Have no idea why, anyone any idea?
Cleaned up Adi Bradfield's sugestion using strrev and for/foreach loops, and only get unique results.
function search_get_combos($array = array()) {
sort($array);
$terms = array();
for ($dec = 1; $dec < pow(2, count($array)); $dec++) {
$curterm = array();
foreach (str_split(strrev(decbin($dec))) as $i => $bit) {
if ($bit) {
$curterm[] = $array[$i];
}
}
if (!in_array($curterm, $terms)) {
$terms[] = $curterm;
}
}
return $terms;
}
The Idea is simple. Suppose you know how to permute, then if you save these permutations in a set it becomes a combinations. Set by definition takes care of the duplicate values. The Php euqivalent of Set or HashSet is SplObjectStorage and ArrayList is Array. It should not be hard to rewrite. I have an implementation in Java:
public static HashSet<ArrayList<Integer>> permuteWithoutDuplicate(ArrayList<Integer> input){
if(input.size()==1){
HashSet<ArrayList<Integer>> b=new HashSet<ArrayList<Integer>>();
b.add(input);
return b;
}
HashSet<ArrayList<Integer>>ret= new HashSet<ArrayList<Integer>>();
int len=input.size();
for(int i=0;i<len;i++){
Integer a = input.remove(i);
HashSet<ArrayList<Integer>>temp=permuteWithoutDuplicate(new ArrayList<Integer>(input));
for(ArrayList<Integer> t:temp)
t.add(a);
ret.addAll(temp);
input.add(i, a);
}
return ret;
}