18

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.

6
  • Does order matter? Also I think you might have to use recursion. Commented May 23, 2011 at 4:23
  • no the order wont matter. i can order it by the size of the string later, i just need a way to get the subsets Commented May 23, 2011 at 4:31
  • Basically you're just looking for the handshake algorithm? Commented May 23, 2011 at 4:37
  • 1
    php.net/manual/en/function.shuffle.php#88408 is a power-set generator example in php Commented May 23, 2011 at 4:43
  • 1
    precisely, the set of all subsets or the powerset. all this time I had been looking with the wrong keyword. thanks. edit: if u want u can make that an answer,i can vote u up. peace Commented May 23, 2011 at 4:56

5 Answers 5

26

As of PHP 7.4, we can have a nice short powerSet function by using spread operator:

// IN:  ["A", "B", "C"]
// OUT: [[],["A"],["B"],["A","B"],["C"],["A","C"],["B","C"],["A","B","C"]]
function powerSet(array $array) : array {
    // add the empty set
    $results = [[]];

    foreach ($array as $element) {
        foreach ($results as $combination) {
            $results[] = [...$combination, $element];
        }
    }

    return $results;
}
Sign up to request clarification or add additional context in comments.

2 Comments

This saved me over a week of brain cracking, simple and straight to the point.
The best answer!
23

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; 
} 

6 Comments

cool was trying to convert this from the java one, wasnt working this is :)
@FallingBullets is there any way to modify this so it WILL return repeats? ie from (1,2,3) return (2 3)(1 2)(1 3)(1 2 3)(1 1 2)(1 1 3)(2 2 1) etc?
Technically this outputs all subsets bar the empty set. Just a technical point - it's not exactly the power set of an array.
Remove $b=sprintf(...), use $i>>$j&1 instead of $b{$j} == '1' - shorter and faster.
To render $membersand $count obsolete: for($i=1<<count($in);--$i;){$out=[];foreach($in as$j=>$u)if($i‌​>>$j&1)$out[]=$u​];...}. This way (with $i not hitting 0), it´s also guaranteed that $out is never empty, so if $minLength is 1, you don´t need count($out) either.
|
1

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));

Comments

1

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"
  }
}

Comments

0

Following @fbstj answer, I update the function:

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";
}

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.