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";
}
Related
This question already has answers here:
Transpose 2d array, join second level with commas, and join first level with pipes
(5 answers)
Closed 7 months ago.
I have the following array.
$a = array("Algebra", "Arithmetic");
$b = array("08/01/2020", "08/02/2019");
$c = array("08/01/2020", "08/02/2019");
print_r($a);
print_r($b);
print_r($b);
and the output is
Array(
[0] => Algebra
[1] => Arithmetic
)
Array(
[0] => 08/01/2020
[1] => 08/01/2019
)
Array(
[0] => 08/02/2020
[1] => 08/02/2019
)
And I want Array in the following structure.
Array(
[0] => Algebra,08/01/2020,08/02/2020
[1] => Arithmetic,08/01/2019,08/02/2019
)
I have tried $results = array_merge_recursive($a, $b, $c); but its not giving desire output.
Thanks for help in advance.
A simple foreach loop will suffice
$a = array("Algebra", "Arithmetic");
$b = array("08/01/2020", "08/02/2019");
$c = array("08/01/2020", "08/02/2019");
foreach( $a as $i=>$v ) {
$new[] = sprintf( '%s,%s,%s', $v, $b[$i], $c[$i] );
}
Firstly, when you find yourself using sequentially-named variables it almost always means that they should actually be an array:
$a = array("Algebra", "Arithmetic");
$b = array("08/01/2020", "08/02/2019");
$c = array("08/01/2020", "08/02/2019");
$better_array = [$a, $b, $c];
var_dump($better_array);
Output:
array(3) {
[0]=>
array(2) {
[0]=>
string(7) "Algebra"
[1]=>
string(10) "Arithmetic"
}
[1]=>
array(2) {
[0]=>
string(10) "08/01/2020"
[1]=>
string(10) "08/02/2019"
}
[2]=>
array(2) {
[0]=>
string(10) "08/01/2020"
[1]=>
string(10) "08/02/2019"
}
}
Once they're in an proper array you can use array_column()
$out = [];
for($i=0, $c=count($better_array[0]); $i < $c; ++$i) {
$out[] = array_column($better_array, $i);
}
var_dump($out);
Output:
array(2) {
[0]=>
array(3) {
[0]=>
string(7) "Algebra"
[1]=>
string(10) "08/01/2020"
[2]=>
string(10) "08/01/2020"
}
[1]=>
array(3) {
[0]=>
string(10) "Arithmetic"
[1]=>
string(10) "08/02/2019"
[2]=>
string(10) "08/02/2019"
}
}
And if that comma-delimited string is what you actually want, then use implode():
$out = [];
for($i=0, $c=count($better_array[0]); $i < $c; ++$i) {
$out[] = implode(',', array_column($better_array, $i));
}
var_dump($out);
Output:
array(2) {
[0]=>
string(29) "Algebra,08/01/2020,08/01/2020"
[1]=>
string(32) "Arithmetic,08/02/2019,08/02/2019"
}
Lastly, you should avoid print_r() as it tends to produce misleading output. Eg: https://3v4l.org/ThSLb
You don't get anything built-in for this purpose. You need to build a custom function for this. You can try this-
<?php
$a = array("Algebra", "Arithmetic");
$b = array("08/01/2020", "08/01/2019");
$c = array("08/02/2020", "08/02/2019");
function mergeAssoc()
{
// You can get variable number of arguments|array by this.
$args = func_get_args();
$master = array();
foreach ($args as $arg)
{
foreach ($arg as $i => $v)
{
$master[$i][] = $v;
}
}
return $master;
}
$res = mergeAssoc($a, $b, $c);
print_r($res);
Note: It will return a multidimensional array. Not an array of comma-separated values.
Output:
Array
(
[0] => Array
(
[0] => Algebra
[1] => 08/01/2020
[2] => 08/02/2020
)
[1] => Array
(
[0] => Arithmetic
[1] => 08/01/2019
[2] => 08/02/2019
)
)
and if we use foreach then our desire output will be there with array separated by comma.
foreach ($res as $key => $value) {
$result[] = implode(',', $value);
}
and output of print_r($result); is
Array
(
[0] => Algebra,08/01/2020,08/02/2020
[1] => Arithmetic,08/01/2019,08/02/2019
)
I have this two arrays that are generated in two foreach loops and I want to set the first array as keys and the second one as values.
after I use this code
foreach ($difference AS $j) {
$fv = $cate->getFilterValueByFeatureID($j);
foreach ($fv AS $z) {
$array = array(
$j => $z
);
var_dump($array);
}
}
this is what I get
array(1) {
[6]=>
int(15)
}
array(1) {
[6]=>
int(20)
}
array(1) {
[8]=>
int(26)
}
array(1) {
[8]=>
int(27)
}
array(1) {
[8]=>
int(33)
}
and I want this result
array(1){
[6] => array(
[0] => 15
[1] => 20
)
array(1){
[8] => array(
[0] => 26
[1] => 27
[2] => 33
)
Like this (untested)
$result = [];
foreach ($difference AS $j) {
$fv = $cate->getFilterValueByFeatureID($j);
foreach ($fv AS $z) {
if(!isset($result[$j])) $result[$j] = [];
$result[$j][] = $z;
}
}
var_dump($result);
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"
}
i have array an with this result :
Array
(
[A] => Array
(
[0] => A
[1] => B
[2] => C
)
[B] => Array
(
[0] =>AA
[1] =>BB
[2] =>CC
)
[C] => Array
(
[0] =>AAA
[1] =>BBB
[2] =>CCC
)
)
i want to get like rows and print like with this result to each row:
A AA AAA
B BB BBB
C CC CCC
how to use foreach to print that result?
foreach ($result as $kk => $arr)
{
foreach($arr as $k=>$v)
{
if ( $k == 'A')
echo $arr[0];
if ( $k == 'B')
echo $arr[1];
if ( $k == 'B')
echo $arr[2]."<br />";
}
}
Create a new tmp variable for storing our new order.
$tmp = array();
How deep will the array go? In your example we go down 3 levels..
$depth = 3;
The array you want to sort
$result = array(
'a' => array( 'a', 'b', 'c' ),
'b' => array( 'aa', 'bb', 'cc' ),
'c' => array( 'aaa', 'bbb', 'ccc' ),
);
For each level in $result down to $depth.
for ($i=0; $i<$depth; $i++)
{
// Loop true our results and push them in to the right position in our $tmp array.
foreach ($result as $row)
{
$tmp[$i][] = $row[$i];
}
}
Output var_dump($tmp):
array(3) {
[0]=>
array(3) {
[0]=>
string(1) "a"
[1]=>
string(2) "aa"
[2]=>
string(3) "aaa"
}
[1]=>
array(3) {
[0]=>
string(1) "b"
[1]=>
string(2) "bb"
[2]=>
string(3) "bbb"
}
[2]=>
array(3) {
[0]=>
string(1) "c"
[1]=>
string(2) "cc"
[2]=>
string(3) "ccc"
}
}
And of course.. to print out your re-ordered array with foreach:
foreach($tmp as $row) {
echo "{$row[0]} {$row[1]} {$row[2]}";
}
will give you:
a aa aaa
b bb bbb
c cc ccc
If you denote your input as $hash, then $result will have the array in the form that you need:
$arr = array_values($hash);
$result = array();
$len = count($arr);
$lenNested = count($arr[0]);
for($i = 0; $i < $len; $i++){
for($j = 0; $j < $lenNested; $j++){
$result[$j][$i] = $arr[$i][$j];
}
}
This is just transposition of $hash. Now you can print out $result line by line.
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),
)));