how to write a function for array manipulation? - php

I need to create a logical relationships without inbreeding ...(animal crossing)
A B C D E
Each letter is below a family, which can vary from 2 to N (user input)
Array:
a b c d e f
Step 1 (print: )
ab bc cd de ef fa
Step 2 (print: )
abcd bcde cdef defa efab fabc
end there, because for example if you try to cross the abcd everyone else has at least one letter
SOME RULES
- The elements cannot be repeated at any stage eg: AA ou BB..CC..DD
- Elements of the groups may not be present in the next ... Example on the 3rd stage:
STRING: AB BC CD DE EA
WRONG -> AB BC
CORRECT -> AB CD
First stage, just print each element of array
Second Stage, print each element with the next, and the last with the first…
Three stage, print the 4 elements each family…
Any idea?
UPDATE
In my database i have an array:
eg:
["Fish1","FishEx3","FishExample","FishSpecie","FishOtherSpecie"]
I need use this function for parse this.

It's not a pretty solution but it works. PHP Fiddle
function Crossbreed($species = array())
{
if (empty($species)) {
return array();
}
$half_way = array();
$output = array();
$species_length = count($species) - 1;
foreach ($species as $index => $specie) {
$next = $index + 1;
if ($index >= $species_length) {
$next = $index - $species_length;
}
$half_way[] = $specie . $species[$next];
}
foreach ($half_way as $index => $specie) {
$next = $index + 2;
if ($next >= $species_length + 1) {
$next = $next - $species_length - 1;
}
$output[] = $specie . $half_way[$next];
}
return $output;
}
$species = array("Fish1","FishEx3","FishExample","FishSpecie","FishOtherSpecie");
$crossbred = Crossbreed($species);
Output of the $half_way variable (Did it with numbers in the array so its easy to read)
Array (
[0] => 12
[1] => 23
[2] => 34
[3] => 45
[4] => 56
[5] => 67
[6] => 78
[7] => 89
[8] => 91
)
Output of the function (Did it with numbers in the array so its easy to read)
Array (
[0] => 1234
[1] => 2345
[2] => 3456
[3] => 4567
[4] => 5678
[5] => 6789
[6] => 7891
[7] => 8912
[8] => 9123
)

Use this functions:
$arr = ['a', 'b', 'c', 'd', 'e', 'f'];
print_stage1($arr);
print_stage2($arr);
print_stage3($arr);
function increment($total, $pos, $inc){
if($pos + $inc < $total)
return $pos + $inc;
else
return ($pos + $inc) - $total;
}
function print_stage1($arr){
foreach($arr as $key => $family){
print $arr[$key]. " ";
}
}
function print_stage2($arr){
$total = count($arr);
foreach($arr as $key => $family){
$next_key = increment($total, $key, 1);
print $arr[$key] . $arr[$next_key]. " ";
}
}
function print_stage3($arr){
$total = count($arr);
foreach($arr as $key => $family){
$next1 = increment($total, $key, 1);
$next2 = increment($total, $key, 2);
$next3 = increment($total, $key, 3);
print $arr[$key] . $arr[$next1]. $arr[$next2]. $arr[$next3]. " ";
}
}

What you're explaining is a pretty simple unique combinatorics problem that can be solved with a stack. By shifting/popping one element at a time off the array/stack and concatenating it with another element you eventually exhaust your array. Repeat this as many times as you'd like for further combinatorics.
Here's an example.
function combine(Array $stack)
{
$newStack = [];
$lastElement = array_shift($stack);
while($nextElement = array_shift($stack)) { // shift
$newStack[] = $lastElement . $nextElement; // push
$lastElement = $nextElement;
}
return $newStack;
}
var_dump($level1 = combine(['a','b','c','d','e','f']), $level2 = combine($level1));
Result is ...
array(5) {
[0]=>
string(2) "ab"
[1]=>
string(2) "bc"
[2]=>
string(2) "cd"
[3]=>
string(2) "de"
[4]=>
string(2) "ef"
}
array(4) {
[0]=>
string(4) "abbc"
[1]=>
string(4) "bccd"
[2]=>
string(4) "cdde"
[3]=>
string(4) "deef"
}

Related

How do i use array_filter() on a mapped array(array_map()) in php?

In the following code I have two arrays $x and $m. I then mapped the two arrays as $k but what I want is that after the two arrays are mapped, if there is an empty element on either of the array $x and $m ($x in this case) then it needs to filter out the corresponding mapped elements from $k as well which is the mapped array.
<!DOCTYPE html>
<html>
<body>
<?php
$x = array("apple", "", 2, null, -5, "orange", 10, false, "",22);
var_dump(array_filter($x));
echo "<br/>";
$m = [12,13,14,15,16,17,18,19,20,21];
$k = array_map(null, $x, $m);
array_filter($k);
shuffle($k);
$count = 1;
foreach ($k as $a) {
if($count <= 8){
echo "The number is: $a[0]. $a[1]. <br>";
}else{
break;
}
$count++;
}
?>
</body>
</html>
But this doesn't seem to work. The output I get is the following:
array(6) { [0]=> string(5) "apple" [2]=> int(2) [4]=> int(-5) [5]=> string(6) "orange" [6]=> int(10) [9]=> int(22) }
The number is: . 19.
The number is: -5. 16.
The number is: apple. 12.
The number is: 22. 21.
The number is: . 20.
The number is: . 15.
The number is: . 13.
The number is: 2. 14.
What I want is that . 20 or . 15 etc be filtered out. How do I go about solving this?
As noticed in comments - array_filter does not modify the array, instead - it returns new array, so your code can be rewritten as:
<?php
$x = array("apple", "", 2, null, -5, "orange", 10, false, "",22);
$filteredX = array_filter($x);
var_dump($filteredX);
echo "<br/>";
$m = [12,13,14,15,16,17,18,19,20,21];
$k = array_map(null, $filteredX, $m);
$filteredK = array_filter($k);
shuffle($filteredK);
$count = 1;
foreach ($filteredK as $a) {
if($count <= 8){
echo "The number is: $a[0]. $a[1]. <br>";
}else{
break;
}
$count++;
}
?>
But! As array_map with null callback returns array of arrays, array_filter does not filter elements that have both elements not empty.
What shows print_r($k) (excerpt):
Array
(
[0] => Array
(
[0] => apple
[1] => 12
)
[1] => Array
(
[0] => 2
[1] => 13
)
[2] => Array
(
[0] => -5
[1] => 14
)
[3] => Array
(
[0] => orange
[1] => 15
)
[4] => Array
(
[0] => 10
[1] => 16
)
[5] => Array
(
[0] => 22
[1] => 17
)
[6] => Array
(
[0] =>
[1] => 18
)
[7] => Array
(
[0] =>
[1] => 19
)
As you can see, elements under indices 6,7 and more have empty value under key [0]. As array_filter filters only plain values, to clear your array from subarrays with empty first element, you should use array_filter with a callback:
$k = array_map(null, $filteredX, $m);
$filteredK = array_filter($k, function($v) { return isset($v[0], $v[1]);});
shuffle($filteredK);
After this filtering you will not get empty values. Final fiddle here.
$x = array("apple", "", 2, null, -5, "orange", 10, false, "",22);
array_filter($x);
echo "<br/>";
$m = [12,13,14,15,16,17,18,19,20,21];
$k = array_map(null, $x, $m);
/* This is the filter code */
$k = array_filter($k, function($value) {
return (bool) $value[0] && (bool) $value[1];
});
/***************************/
shuffle($k);
$count = 1;
foreach ($k as $a) {
if($count <= 8){
echo "The number is: $a[0]. $a[1]. <br>";
}else{
break;
}
$count++;
}

Parsing nested curly brace with php regex

I want to parse raw string with nested curly braces to multidimensional arrays. Below I added sample code which works. But main problem my regex captures only first matched group and ignores another occurences.
Any help highly appreciated.
Code:
$regex = '/(?ims)(group [a-z0-9\s\,\.\:#_\-#]+)\{([^\}]*)\}/';
preg_match_all( $regex, file_get_contents('data.txt', FILE_USE_INCLUDE_PATH), $arr);
$result = array();
foreach ($arr[0] as $i => $x)
{
$selector = trim($arr[1][$i]);
$rules = explode(';', trim($arr[2][$i]));
$result[$selector] = array();
foreach ($rules as $strRule)
{
if (!empty($strRule))
{
$rule = explode(" = ", $strRule);
$result[$selector][][trim($rule[0])] = trim($rule[1]);
}
}
}
Raw string (data.txt):
group A { T1 { X = 44; }
T2 { Y = 33; } }
group B { T1 { X = 555; } }
Code Output:
Array (
[group A] => Array (
[0] => Array (
[T1 { X] => 44
)
)
[group B] => Array (
[0] => Array (
[T1 { X] => 555
)
)
)
But excepted Output:
Array(
[group A] => Array(
[T1] => Array(
[X] => 44
)
[T2] => Array (
[Y] => 33
)
)
[group B] => Array(
[T1] => Array(
[X] => 555
)
)
)
A better solutions might exists, but it works.
Ensure your input cannot have deeper values, or you have to improve/rewrite the entiere rule !
$input = <<<REG
group A { T1 { X = 44; }
T2 { Y = 33; } }
group B { T1 { X = 555; } }
REG;
$out = [];
$cbuff = null;
$rule = "/^(?<group>[^\{\}]*)?\{|((?<set>[\w\s]+)\{(?<var>[\w\s]+)=(?<val>[\d\s]+)[;\s]+\})/im";
preg_replace_callback($rule, function($m)
use(&$out, &$cbuff) // import locally our working requirements
{
$m = array_filter($m, "is_string", ARRAY_FILTER_USE_KEY); // cleaning
$m = array_map("trim", array_filter($m)); // cleaning and trim
if(key($m)=="group") // get & create the current root group
$out[($cbuff = reset($m))] = [];
if(isset($m["var"])) // assuming 'var' exists, you'll get 'set/var' too
{
if(is_null($cbuff)) return; // prevent no group found
$out[$cbuff][$m["set"]] = [$m["var"] => $m["val"]];
}
}, $input);
var_dump($out);
Will output:
array(2) {
["group A"]=>
array(2) {
["T1"]=>
array(1) {
["X"]=>
string(2) "44"
}
["T2"]=>
array(1) {
["Y"]=>
string(2) "33"
}
}
["group B"]=>
array(1) {
["T1"]=>
array(1) {
["X"]=>
string(3) "555"
}
}
}
Hint
I think you are ok with PHP,
but i put a little bit of explaination about regexes:
first i've used "named group" for each parts 'set/var/val' pattern: ?<name>
That confer an own group key under the matching output
(?<set>...)|(?<var>...) gives --> ["set" => ...], ["var" => "..."]
Finally, to separate the sub properties to root group,
i'm just matching any part does not contains brackets
Bench
You can retrive my bench on regex101
3 matches, 60 steps (~0ms)
5 matches, 107 steps (~0ms)
yes it can be optimized!

Making values unique in multidimensional array

I have some Problems reducing a multidimensional array into a normal one.
I have an input array like this:
Array
(
[0] => Array (
[0] => 17
[1] => 99
)
[1] => Array (
[0] => 17
[1] => 121
)
[2] => Array (
[0] => 99
[1] => 77
)
[3] => Array (
[0] => 45
[1] => 51
)
[4] => Array (
[0] => 45
[1] => 131
)
So I have a multidimensional array with some overlaps in the values (eg 17,99 and 17,121)
Now I want to have an output like this:
Array
(
[0] => Array (
[0] => 17
[1] => 99
[2] => 121
[3] => 77
)
[2] => Array (
[0] => 45
[1] => 51
[3] => 131
)
I want to save, which articles are the same in my database this way. The output array shpuld still be a multidimesional array, but every number on the second level should be unique in the array.
I'm trying to solve this for more than a week now, but I dont get it to work. I know it should be easy...but anyway - I dont get it :D
This is what i got so far:
$parity_sorted = array();
foreach($arr as $key => $a){
if(count($parity_sorted) > 0){
foreach($parity_sorted as $key2 => $arr_new){
if(in_array($a[0], $arr_new) || in_array($a[1], $arr_new)){
if(!in_array($a[0], $arr_new)){array_push($parity_sorted[$key2], $a[0]);}
if(!in_array($a[1], $arr_new)){array_push($parity_sorted[$key2], $a[1]);}
} else {
array_push($parity_sorted, array($a[0],$a[1]));
}
}
} else {
array_push($parity_sorted, array($a[0],$a[1]));
}
}
Did you maybe already solve problem like this or is there a much easier way? Maybe I just think too complicated (It's not my first try, but this code was the last try)
Any help would be appreciated. Thanks a lot
Here is my revised code given your comment and a DEMO of it working as expected. ( http://codepad.org/CiukXctS )
<?php
$tmp = array();
foreach($array as $value)
{
// just for claraty, let's set the variables
$val1 = $value[0];
$val2 = $value[1];
$found = false;
foreach($tmp as &$v)
{
// check all existing tmp for one that matches
if(in_array($val1, $v) OR in_array($val2, $v))
{
// this one found a match, add and stop
$v[] = $val1;
$v[] = $val2;
// set the flag
$found = true;
break;
}
}
unset($v);
// check if this set was found
if( ! $found)
{
// this variable is new, set both
$tmp[] = array(
$val1,
$val2,
);
}
}
// go trough it all again to ensure uniqueness
$array = array();
foreach($tmp as $value)
{
$array[] = array_unique($value); // this will eliminate the duplicates from $val2
}
ORIGIN ANSWER
The question is badly asked, but I'll attempt to answer what I believe the question is.
You want to gather all the pairs of arrays that have the same first value in the pair correct?
$tmp = array();
for($array as $value)
{
// just for claraty, let's set the variables
$val1 = $value[0];
$val2 = $value[1];
if(isset($tmp[$val1])) // we already found it
{
$tmp[$val1][] = $val2; // only set the second one
}
else
{
// this variable is new, set both
$tmp[$val1] = array(
$val1,
$val2,
);
}
}
// go trough it all again to change the index to being 0-1-2-3-4....
$array = array();
foreach($tmp as $value)
{
$array[] = array_unique($value); // this will eliminate the duplicates from $val2
}
Here is solution for common task.
$data = array(array(17,99), array(17,121), array(99,77), array(45,51), array(45,131));
$result = array();
foreach ($data as $innner_array) {
$intersect_array = array();
foreach ($result as $key => $result_inner_array) {
$intersect_array = array_intersect($innner_array, $result_inner_array);
}
if (empty($intersect_array)) {
$result[] = $innner_array;
} else {
$result[$key] = array_unique(array_merge($innner_array, $result_inner_array));
}
}
var_dump($result);
Try:
$arr = array(array(17,99),
array(17,121),
array(99,77),
array(45, 51),
array(45, 131)
);
foreach($arr as $v)
foreach($v as $m)
$new_arr[] = $m;
$array = array_chunk(array_unique($new_arr), 4);
var_dump($array);
Demo
It uses array_unique and array_chunk.
Output:
array(2) { [0]=>array(4) { [0]=> int(17) [1]=>int(99)
[2]=>int(121) [3]=> int(77) }
[1]=> array(3) { [0]=> int(45) [1]=>int(51)
[2]=>int(131) }
}
I think I get your problem. Let me have a crack at it.
$firstElems = array();
$secondElems = array();
foreach ( $arr as $v ) {
$firstElems[ $v[0] ] = array( $v[0] );
}
foreach ( $arr as $v ) {
$secondElems[ $v[1] ] = $v[0];
}
foreach ( $arr as $v ) {
if ( isset( $secondElems[ $v[0] ] ) ) {
array_push( $firstElems[ $secondElems[ $v[0] ] ], $v[1] );
}
else {
array_push( $firstElems[ $v[0] ], $v[1] );
}
}
foreach ( $firstElems as $k => $v ) {
if ( isset( $secondElems[ $k ] ) ) {
unset( $firstElems[ $k ] );
}
}
Output:
Array
(
[17] => Array
(
[0] => 17
[1] => 99
[2] => 121
[3] => 77
)
[45] => Array
(
[0] => 45
[1] => 51
[2] => 131
)
)
(Code examples: http://codepad.org/rJNNq5Vd)
I truly believe I understand you and if this is the case here is what you're looking for:
function arrangeArray($array) {
$newArray = array(array_shift($array));
for ($x = 0; $x < count($newArray); $x++) {
if (!is_array($newArray[$x])) {
unset($newArray[$x]);
return $newArray;
}
for ($i = 0; $i < count($newArray[$x]); $i++) {
foreach ($array as $key => $inArray) {
if (in_array($newArray[$x][$i], $inArray)) {
$newArray[$x] = array_unique(array_merge($newArray[$x], $inArray));
unset($array[$key]);
}
}
}
$newArray[] = array_shift($array);
}
}
Which will return:
array(2) {
[0]=>
array(4) {
[0]=>
int(17)
[1]=>
int(99)
[2]=>
int(121)
[4]=>
int(77)
}
[1]=>
array(3) {
[0]=>
int(45)
[1]=>
int(51)
[3]=>
int(131)
}
}
For:
var_dump(arrangeArray(array(
array(17,99),
array(17,121),
array(99,77),
array(45, 51),
array(45, 131),
)));
And:
array(1) {
[0]=>
array(6) {
[0]=>
int(17)
[1]=>
int(99)
[2]=>
int(121)
[3]=>
int(45)
[4]=>
int(77)
[6]=>
int(51)
}
}
For:
var_dump(arrangeArray(array(
array(17,99),
array(17,121),
array(99,77),
array(45, 51),
array(45, 17),
)));

Generate array of random unique numbers in PHP

I'm trying to generate an array of random numbers from 0-n then shuffle (but ensure that the keys and values DO NOT match).
For example:
0 => 3
1 => 2
2 => 4
3 => 0
4 => 1
Note that both keys and values are from 0-4 but none of the keys and values are the same.
Any thoughts?
A even shorter solution:
$random_number_array = range(0, 100);
shuffle($random_number_array );
$random_number_array = array_slice($random_number_array ,0,10);
print_r($random_number_array);
Result will be:
[0] => 53
[1] => 6
[2] => 16
[3] => 59
[4] => 8
[5] => 18
[6] => 62
[7] => 39
[8] => 22
[9] => 26
$max = 5;
$done = false;
while(!$done){
$numbers = range(0, $max);
shuffle($numbers);
$done = true;
foreach($numbers as $key => $val){
if($key == $val){
$done = false;
break;
}
}
}
This will generate an array with 10 random numbers from 0 to 100:
array_map(function () {
return rand(0, 100);
}, array_fill(0, 10, null));
Result:
array(10) {
[0]=>
int(15)
[1]=>
int(97)
[2]=>
int(20)
[3]=>
int(64)
[4]=>
int(57)
[5]=>
int(38)
[6]=>
int(16)
[7]=>
int(53)
[8]=>
int(56)
[9]=>
int(22)
}
Explanation:
array_fill(0, 10, null) will generate an array with 10 empty items
array_map Applies the callback (first argument) to each item of the array it receives (second argument). In this example, we just return a random number for each array item.
Playground: https://3v4l.org/FffN6
If you need to make sure each generated number is unique:
$uniqueNumbers = 100;
$picked = [];
$uniqueRandomNumbers = array_map(function () use(&$picked, $uniqueNumbers) {
do {
$rand = rand(0, $uniqueNumbers);
} while(in_array($rand, $picked));
$picked[] = $rand;
return $rand;
}, array_fill(0, $uniqueNumbers, null));
https://3v4l.org/mSWBo#v8.0.9
Naive solution:
$n = 10;
$rands = array();
for($i=0; $i<$n;$i++) {
$ok = false;
while(!$ok) {
$x=mt_rand(0,$n-1);
$ok = !in_array($x, $rands) && $x != $i;
}
$rands[$i]=$x;
}
var_dump($rands);
Efficient solution:
$n = 100;
$numbers = range(0, $n-1);
$rands = array();
for ($i=0; $i < $n; $i++) {
$ok = false;
while (!$ok) {
$x = array_rand($numbers);
$ok = !in_array($numbers[$x], $rands) && $numbers[$x] != $i;
}
$rands[$i] = $numbers[$x];
unset($numbers[$x]);
}
var_dump($rands);
edit: s/rand/mt_rand/
edit #2: both solutions can end up in a deadlock, as mentioned by #AMayer. I stand corrected.
Here's a rather long, but also pretty efficient solution, I believe. Contrary to other solutions posted here, this cannot deadlock (unless $size<2), and this will not do a full shuffle every time one value doesn't fit. Instead, it will only replace that value with another, random value.
function unique_list($size=5) {
function all_unique($numbers) {
foreach ($numbers as $key=>$value)
if ($key==$value) return false;
return true;
}
function flip($a, $b, &$numbers) {
$numbers[$a] = $numbers[$a] + $numbers[$b];
$numbers[$b] = $numbers[$a] - $numbers[$b];
$numbers[$a] = $numbers[$a] - $numbers[$b];
}
$flip_count = 0;
$numbers = range(0,$size-1);
shuffle($numbers);
while (!all_unique($numbers)) {
foreach ($numbers as $key=>$value) {
if ($key==$value) {
flip($key, rand(0,$size-1), $numbers);
$flip_count++;
break;
}
}
}
printf("Flipped %d values\n", $flip_count);
return $numbers;
}
$list = unique_list(10);
print_r($list);
The above will print something similar to
Flipped 1 value(s)
Array
(
[0] => 2
[1] => 5
[2] => 7
[3] => 9
[4] => 6
[5] => 3
[6] => 1
[7] => 8
[8] => 0
[9] => 4
)
What's about combining range(...) (for generating the original array first), shuffle(...) (to randomize the array), and array_intersect_assoc(...) (for checking the result array)?
$numbers = range(0, 4);
do {
shuffle($numbers); // print_r($numbers);
$matchingKeyValuePairs = array_intersect_assoc(array_keys($numbers), array_values($numbers)); // print_r($matchingKeyValuePairs);
} while (! empty($matchingKeyValuePairs));
This solution might bring some performance issues for big numbers of elements. But it can be extended by a logic for dealing with the $matchingKeyValuePairs. So while now the logic is like "IF there are matchingKeyValuePairs THEN try it again", a much more efficient logic migth be "IF there are matchingKeyValuePairs THEN cut the matchingKeyValuePairs from the array, randomize this sub-array (multiple times, if needed), and merge it back".
The approach here is to create a range of numbers from 0 to n, and then shuffle.
We then look for key value matches, and swap pairs of them. If we don't have a pair to swap, we swap with the last item, unless the item is the last item, then we swap with the first.
<?php
$n = 5;
$values = range(0, $n);
shuffle($values);
$matched = array_filter($values, function($v, $k) {return $k === $v;}, ARRAY_FILTER_USE_BOTH);
foreach(array_chunk($matched, 2) as list($a, $b)) {
if($b === null) {
$swap_key = array_key_last($values);
if($swap_key == $a) {
$swap_key = array_key_first($values);
}
list($values[$a], $values[$swap_key]) = [$values[$swap_key], $a];
} else {
list($values[$a], $values[$b]) = [$b, $a];
}
}
Example shuffle when n is 5:
array (
0 => 0,
1 => 5,
2 => 1,
3 => 3,
4 => 4,
5 => 2,
)
Here we have matches for keys:
0, 3 and 4.
So we swap the values for keys 0 and 3, and 4 with the last.
array (
0 => 3,
1 => 5,
2 => 1,
3 => 0,
4 => 2,
5 => 4,
)
(array_key_first could be swapped for 0 given the range here. I've left it as it is more explicit.)
public function getUniqueArray($value, $total)
{
$array = [];
for ($i = 0; $i < $value; $i++) {
$rand = rand(0, $total);
if (in_array($rand, $array))
$i--;
else
array_push($array, rand(0, $total));
}
return $array;
}

Finding the subsets of an array in PHP

I have a Relational Schema with attributes (A B C D).
I have a set of Functional Dependencies with me too.
Now I need to determine the closure for all the possible subsets of R's attributes. That's where I am stuck. I need to learn how to find subsets (non-repeating) in PHP.
My Array is stored like this.
$ATTRIBUTES = ('A', 'B', 'C', 'D').
so my subsets should be
$SUBSET = ('A', 'B', 'C', 'D', 'AB', 'AC', AD', 'BC', 'BD', 'CD', 'ABC', 'ABD', 'BCD', 'ABCD')
The code shouldn't be something big but for some reason I can't get my head around it.
Using php array_merge we can have a nice short powerSet function
function powerSet($array) {
// add the empty set
$results = [[]];
foreach ($array as $element) {
foreach ($results as $combination) {
$results[] = array_merge(array($element), $combination);
}
}
return $results;
}
You wish for the power set of $attributes? That is what your question implies.
An example can be found here (quoted for completeness)
<?php
/**
* Returns the power set of a one dimensional array, a 2-D array.
* [a,b,c] -> [ [a], [b], [c], [a, b], [a, c], [b, c], [a, b, c] ]
*/
function powerSet($in,$minLength = 1) {
$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++) {
if ($b{$j} == '1') $out[] = $in[$j];
}
if (count($out) >= $minLength) {
$return[] = $out;
}
}
return $return;
}
Here a backtracking solution.
given a function that returns all the L-lenght subsets of the input set, find all the L-lenght subsets from L = 2 to dataset input length
<?php
function subsets($S,$L) {
$a = $b = 0;
$subset = [];
$result = [];
while ($a < count($S)) {
$current = $S[$a++];
$subset[] = $current;
if (count($subset) == $L) {
$result[] = json_encode($subset);
array_pop($subset);
}
if ($a == count($S)) {
$a = ++$b;
$subset = [];
}
}
return $result;
}
$S = [ 'A', 'B', 'C', 'D'];
$L = 2;
// L = 1 -> no need to do anything
print_r($S);
for ($i = 2; $i <= count($S); $i++)
print_r(subsets($S,$i));
Based on #Yada's answer, this will generate the power set of an array, but preserve the original array's keys in each subset (the return value is still numerically & sequentially indexed). This very useful if you need subsets of an associative array.
The subsets also retain the element order of the original array. I added a stable sort to $results because I needed it, but you can omit it.
function power_set($array) {
$results = [[]];
foreach ($array as $key => $value) {
foreach ($results as $combination) {
$results[] = $combination + [$key => $value];
}
}
# array_shift($results); # uncomment if you don't want the empty set in your results
$order = array_map('count', $results);
uksort($results, function($key_a, $key_b) use ($order) {
$comp = $order[$key_a] - $order[$key_b]; # change only this to $order[$key_b] - $order[$key_a] for descending size
if ($comp == 0) {
$comp = $key_a - $key_b;
}
return $comp;
});
return array_values($results);
}
Given OP's input, var_dump(power_set(['A', 'B', 'C', 'D'])); provides:
array(16) {
[0] =>
array(0) {
}
[1] =>
array(1) {
[0] =>
string(1) "A"
}
[2] =>
array(1) {
[1] =>
string(1) "B"
}
[3] =>
array(1) {
[2] =>
string(1) "C"
}
[4] =>
array(1) {
[3] =>
string(1) "D"
}
[5] =>
array(2) {
[0] =>
string(1) "A"
[1] =>
string(1) "B"
}
[6] =>
array(2) {
[0] =>
string(1) "A"
[2] =>
string(1) "C"
}
[7] =>
array(2) {
[1] =>
string(1) "B"
[2] =>
string(1) "C"
}
[8] =>
array(2) {
[0] =>
string(1) "A"
[3] =>
string(1) "D"
}
[9] =>
array(2) {
[1] =>
string(1) "B"
[3] =>
string(1) "D"
}
[10] =>
array(2) {
[2] =>
string(1) "C"
[3] =>
string(1) "D"
}
[11] =>
array(3) {
[0] =>
string(1) "A"
[1] =>
string(1) "B"
[2] =>
string(1) "C"
}
[12] =>
array(3) {
[0] =>
string(1) "A"
[1] =>
string(1) "B"
[3] =>
string(1) "D"
}
[13] =>
array(3) {
[0] =>
string(1) "A"
[2] =>
string(1) "C"
[3] =>
string(1) "D"
}
[14] =>
array(3) {
[1] =>
string(1) "B"
[2] =>
string(1) "C"
[3] =>
string(1) "D"
}
[15] =>
array(4) {
[0] =>
string(1) "A"
[1] =>
string(1) "B"
[2] =>
string(1) "C"
[3] =>
string(1) "D"
}
}
Following #fbstj answer, I update the function:
Use bitwize operators instead of sprintf (#Titus comments)
Handle empty set (#James Stott & #fbstj comments)
Update syntax to PHP 7+
function powerSet(array $in, int $minLength = 0): array
{
$return = [];
if ($minLength === 0) {
$return[] = [];
}
for ($i = 1 << count($in); --$i;) {
$out = [];
foreach ($in as $j => $u) {
if ($i >> $j & 1) {
$out[] = $u;
}
}
if (count($out) >= $minLength) {
$return[] = $out;
}
}
return $return;
}
Since power set functions can increase by a lot the memory load (2count($in) iterations), consider using Generator:
function powerSet(array $in, int $minLength = 0): \Generator
{
if ($minLength === 0) {
yield [];
}
for ($i = 1 << count($in); --$i;) {
$out = [];
foreach ($in as $j => $u) {
if ($i >> $j & 1) {
$out[] = $u;
}
}
if (count($out) >= $minLength) {
yield $out;
}
}
}
Usage:
foreach (powerSet(range(1, 10)) as $value) {
echo implode(', ', $value) . "\n";
}

Categories