40

I have numbers, from 0 to 8. I would like in result, all possible sets of those numbers, each set should use all numbers, each number can occur only once in a set.

I would like to see solution made in PHP that could print out result. Or, at least, I would like some refreshment in theory of combinatorics, as I have long forgotten it. What is the formula to calculate how many permutations will there be?

Example sets:

  • 0-1-2-3-4-5-6-7-8
  • 0-1-2-3-4-5-6-8-7
  • 0-1-2-3-4-5-8-6-7
  • 0-1-2-3-4-8-5-6-7
  • 0-1-2-3-8-4-5-6-7
  • 0-1-2-8-3-4-5-6-7
  • and so on...
Mr.Wizard
  • 24,179
  • 5
  • 44
  • 125
Deele
  • 3,728
  • 2
  • 33
  • 51

12 Answers12

58

You're looking for the permutations formula:

nPk = n!/(n-k)!

In your case, you have 9 entries and you want to choose all of them, that's 9P9 = 9! = 362880

You can find a PHP algorithm to permutate in recipe 4.26 of O'Reilly's "PHP Cookbook".

pc_permute(array(0, 1, 2, 3, 4, 5, 7, 8));

Copied in from O'Reilly:

function pc_permute($items, $perms = array( )) {
    if (empty($items)) { 
        print join(' ', $perms) . "\n";
    }  else {
        for ($i = count($items) - 1; $i >= 0; --$i) {
             $newitems = $items;
             $newperms = $perms;
             list($foo) = array_splice($newitems, $i, 1);
             array_unshift($newperms, $foo);
             pc_permute($newitems, $newperms);
         }
    }
}
Charlie
  • 11,380
  • 19
  • 83
  • 138
yzxben
  • 862
  • 6
  • 11
  • woah that's crazy. Maybe this belongs on the mathematics site for stack exchange, but why is 0! = 1 – Jason Mar 31 '11 at 22:13
  • Do you know any algorithm, to generate all of them? :) I can reduce ammount to 8!=40320, as I know, that first number, can be only 0 1 or 4. And 40k is not too much... – Deele Mar 31 '11 at 22:15
  • 3
    @Jason: It's per definition 0. Just as `x^0 = 1` (except for `x = 0`, because `0^0 = 0`). – poke Mar 31 '11 at 22:18
  • 1
    @Deele If you want permutations that start with 0 1 or 4, you're looking at three different permutations of 8 digits (different sets), so it's 8!+8!+8! = 120960 – yzxben Mar 31 '11 at 22:23
  • yeah, I already did the math :) I think, I will use one of those algorithms given here, to write all of results in file, afterwards, will read them to filter out the ones I need. – Deele Mar 31 '11 at 22:26
  • Jason and Poke, you misunderstand the meaning of the ! here. In logical expressions it means NOT, but in mathematical context it is the notation for a factorial. Google it. – Ellert van Koperen Jan 16 '16 at 18:00
42

Since PHP 5.5 you can use Generators. Generators save a lot of memory and are way faster (more than half compared to pc_permute()). So if you have any chance of having PHP 5.5 installed, you definitely want Generators. This snipped is ported from Python: https://stackoverflow.com/a/104436/3745311

function permutations(array $elements)
{
    if (count($elements) <= 1) {
        yield $elements;
    } else {
        foreach (permutations(array_slice($elements, 1)) as $permutation) {
            foreach (range(0, count($elements) - 1) as $i) {
                yield array_merge(
                    array_slice($permutation, 0, $i),
                    [$elements[0]],
                    array_slice($permutation, $i)
                );
            }
        }
    }
}

Sample usage:

$list = ['a', 'b', 'c'];

foreach (permutations($list) as $permutation) {
    echo implode(',', $permutation) . PHP_EOL;
}

Output:

a,b,c
b,a,c
b,c,a
a,c,b 
c,a,b
c,b,a
Community
  • 1
  • 1
spezifanta
  • 675
  • 8
  • 11
  • 2
    How would you adapt this to only pick a subset. E.g. to return the above but also a,b | a,c | b,a | b,c | c,a | c,b | a | b | c ? – Codemonkey Mar 10 '15 at 11:20
  • See my answer for a function which allows you to specify the size of the permutations it will generate. From there it is trivial to generate all permutations. – eddiewould Apr 09 '17 at 14:21
27

Since this question often comes up in Google Search results, here's a modified version of the accepted answer that returns all combinations in an array and passes them as a return value of the function.

function pc_permute($items, $perms = array( )) {
    if (empty($items)) {
        $return = array($perms);
    }  else {
        $return = array();
        for ($i = count($items) - 1; $i >= 0; --$i) {
             $newitems = $items;
             $newperms = $perms;
         list($foo) = array_splice($newitems, $i, 1);
             array_unshift($newperms, $foo);
             $return = array_merge($return, pc_permute($newitems, $newperms));
         }
    }
    return $return;
}

To use:

$value = array('1', '2', '3');
print_r(pc_permute($value));
dAngelov
  • 830
  • 6
  • 10
11

I've something that You may like

function combination_number($k,$n){
    $n = intval($n);
    $k = intval($k);
    if ($k > $n){
        return 0;
    } elseif ($n == $k) {
        return 1;
    } else {
        if ($k >= $n - $k){
            $l = $k+1;
            for ($i = $l+1 ; $i <= $n ; $i++)
                $l *= $i;
            $m = 1;
            for ($i = 2 ; $i <= $n-$k ; $i++)
                $m *= $i;
        } else {
            $l = ($n-$k) + 1;
            for ($i = $l+1 ; $i <= $n ; $i++)
                $l *= $i;
            $m = 1;
            for ($i = 2 ; $i <= $k ; $i++)
                $m *= $i;            
        }
    }
    return $l/$m;
}

function array_combination($le, $set){

    $lk = combination_number($le, count($set));
    $ret = array_fill(0, $lk, array_fill(0, $le, '') );

    $temp = array();
    for ($i = 0 ; $i < $le ; $i++)
        $temp[$i] = $i;

    $ret[0] = $temp;

    for ($i = 1 ; $i < $lk ; $i++){
        if ($temp[$le-1] != count($set)-1){
            $temp[$le-1]++;
        } else {
            $od = -1;
            for ($j = $le-2 ; $j >= 0 ; $j--)
                if ($temp[$j]+1 != $temp[$j+1]){
                    $od = $j;
                    break;
                }
            if ($od == -1)
                break;
            $temp[$od]++;
            for ($j = $od+1 ; $j < $le ; $j++)    
                $temp[$j] = $temp[$od]+$j-$od;
        }
        $ret[$i] = $temp;
    }
    for ($i = 0 ; $i < $lk ; $i++)
        for ($j = 0 ; $j < $le ; $j++)
            $ret[$i][$j] = $set[$ret[$i][$j]];   

    return $ret;
}

Here is how to use it:

To get the number of combinations:

combination_number(3,10); // returns number of combinations of ten-elements set.

To get all possible combinations:

$mySet = array("A","B","C","D","E","F");
array_combination(3, $mySet); // returns all possible combinations of 3 elements of six-elements set.

Hope You make use of that.

Piotr Salaciak
  • 1,653
  • 1
  • 15
  • 28
9

I've ported the Python itertools code listed here (using generators). The advantage over the solutions posted so far is that it allows you to specify r (permutation size).

function permutations($pool, $r = null) {
    $n = count($pool);

    if ($r == null) {
        $r = $n;
    }

    if ($r > $n) {
        return;
    }

    $indices = range(0, $n - 1);
    $cycles = range($n, $n - $r + 1, -1); // count down

    yield array_slice($pool, 0, $r);

    if ($n <= 0) {
        return;
    }

    while (true) {
        $exit_early = false;
        for ($i = $r;$i--;$i >= 0) {
            $cycles[$i]-= 1;
            if ($cycles[$i] == 0) {
                // Push whatever is at index $i to the end, move everything back
                if ($i < count($indices)) {
                    $removed = array_splice($indices, $i, 1);
                    array_push($indices, $removed[0]);
                }
                $cycles[$i] = $n - $i;
            } else {
                $j = $cycles[$i];
                // Swap indices $i & -$j.
                $i_val = $indices[$i];
                $neg_j_val = $indices[count($indices) - $j];
                $indices[$i] = $neg_j_val;
                $indices[count($indices) - $j] = $i_val;
                $result = [];
                $counter = 0;
                foreach ($indices as $indx) {
                    array_push($result, $pool[$indx]);
                    $counter++;
                    if ($counter == $r) break;
                }
                yield $result;
                $exit_early = true;
                break;
            }
        }
        if (!$exit_early) {
            break; // Outer while loop
        }
    }
}

It works for me, but no promises! Example usage:

$result = iterator_to_array(permutations([1, 2, 3, 4], 3));
foreach ($result as $row) {
    print implode(", ", $row) . "\n";
}
eddiewould
  • 1,555
  • 16
  • 36
  • 1
    Brilliant. Thank you eddiewould – delboy1978uk Jan 19 '18 at 10:52
  • 1
    You have saved my time, Brilliant! – Meas Jan 06 '19 at 10:43
  • thank you for the solution. I was trying to generate the 5 char permutations of (0-9 and a-z). $result = iterator_to_array(permutations([0,1, 2, 3, 4,5,6,7,8,9,a,b,c,d], 5)); works fine. But when I add [0,1, 2, 3, 4,5,6,7,8,9,a,b,c,d, e] - it stop working. Any idea? – Roy Apr 28 '21 at 10:37
5

This is my version of class. This class builds and returns permutated array as result

class Permutation {
    private $result;

    public function getResult() {
        return $this->result;
    }

    public function permute($source, $permutated=array()) {
        if (empty($permutated)){
            $this->result = array();
        }
        if (empty($source)){
            $this->result[] = $permutated;
        } else {
            for($i=0; $i<count($source); $i++){
                $new_permutated = $permutated;
                $new_permutated[] = $source[$i];
                $new_source =    array_merge(array_slice($source,0,$i),array_slice($source,$i+1));
                $this->permute($new_source, $new_permutated);
            }
        }
        return $this;
    }
}

$arr = array(1,2,3,4,5);
$p = new Permutation();
print_r($p->permute($arr)->getResult());

The last three lines to test my class.

Jeff_Alieffson
  • 2,672
  • 29
  • 34
2

Lexicographical order. There is no recursion. Almost no limits for array length. There is no sort. It's running rather fast. It's easy to understand. Minus: it gives a notice, but you can add a condition to start compare with the second element or error_reporting(0).

$a = array(
1,
2,
3,
4,
5
 );
    $b = array_reverse($a);
    print_r($a);
   //here need "br"
  while ($a != $b)
{
foreach(array_reverse($a, true) as $k => $v)
    {
    if ($v < $a[$k + 1])
        {
        foreach(array_reverse($a, true) as $ka => $val)
            {
            if ($val > $v) break;
            }

        $ch = $a[$k];
        $a[$k] = $a[$ka];
        $a[$ka] = $ch;
        $c = array_slice($a, 0, $k + 1);
        print_r($a = array_merge($c, array_reverse(array_slice($a, $k + 1))));
        //here need "br"
        break;
        }
       }
      }
dcc0
  • 21
  • 4
  • 1
    Fix for notice: `if (isset($a[$key + 1]) && $value < $a[$key + 1]) {` (too short for edit :) – Rarst Jul 10 '17 at 10:09
2

This is a simple recursive function that prints all permutations (written in pseudocode)

function rec(n, k) {
    if (k == n) {
        for i = 0 to n-1
            print(perm[i], ' ');
        print('\n');
    }
    else {
        for i = 0 to n-1 {
            if (not used[i]) {
                used[i] = true;
                perm[k] = i;
                rec(n, k+1);
                used[i] = false;
            }
        }
    }
}

And it is called like this:

rec(9, 0);
fdermishin
  • 3,519
  • 3
  • 24
  • 45
1

Here is my proposal, hope a little bit clearer than accepted answer.

   function permutate($elements, $perm = array(), &$permArray = array())
{
    if(empty($elements))
    {
       array_push($permArray,$perm); return;
    }

    for($i=0;$i<=count($elements)-1;$i++)
    {
       array_push($perm,$elements[$i]);
       $tmp = $elements; array_splice($tmp,$i,1);
       permutate($tmp,$perm,$permArray);
       array_pop($perm);
    }

    return $permArray;
}

and usage:

$p = permutate(array('a','b','c'));
foreach($p as $perm)
    print join(",",$perm)."|\n";
Jarek
  • 45
  • 1
  • 7
1

You're basically talking about permutations where both n and k are 9 so you'll have 9! different permutations; see this: http://en.wikipedia.org/wiki/Permutation.

Argote
  • 2,155
  • 1
  • 15
  • 20
0
//function call
print_r(combinations([1,2,3,4,5,6,7,8,9,10,11,12,13]));
/**
 * @param $mainArray
 * @param int $size - optional
 * @param array $combinations - optional
 * @return mixed
 */
function combinations($mainArray, $size = 3, $combinations = [])
{
    if (empty($combinations)) {
        $combinations = $mainArray;
    }
    if ($size == 1) {
        return str_replace('-','',$combinations);;
    }
    $newCombination = array();
    foreach ($mainArray as $key => $val){
        foreach ($combinations as $char) {
            if(in_array($val, explode('-', $char))){
                continue;
            }
            $newCombination[] = $val . '-' . $char;
        }
    }
    return combinations($mainArray, $size - 1, $newCombination);
}

//========================= Next solution ==================================

function sampling($chars, $size, $combinations = array()) {
    # if it's the first iteration, the first set 
    # of combinations is the same as the set of characters
    if (empty($combinations)) {
        $combinations = $chars;
    }
    # we're done if we're at size 1
    if ($size == 1) {
        return $combinations;
    }
    # initialise array to put new values in
    $new_combinations = array();
    # loop through existing combinations and character set to create strings
    foreach ($combinations as $combination) {
        foreach ($chars as $char) {
            $new_combinations[] = $combination .'-'. $char ; 

        }
    }
    # call same function again for the next iteration
    return $this->sampling($chars, $size - 1, $new_combinations);
}
function array_has_dupes($array) {
   return count($array) !== count(array_unique($array));
}   
function total() {
    // Generate ticket price
    $arrfinal = array();
    // combinations
    $chars = array(1,2,3,4,5,6,7,8,9,10,11,12,13); // for 10 digits
    $combinations = $this->sampling($chars, 3);
    //print_r($combinations); //exit;

    foreach($combinations as $key => $val)
    {
        $arr = explode('-', $val);//str_split($val);
        if(!$this->array_has_dupes($arr)){
            $arrfinal[] = str_replace('-', '', $val);
        }
    }
  echo '<pre>'; print_r($arrfinal); echo '</pre>';
}
  • 3
    Add some context to support your code Pravin... It makes it easier, for the OP and others who are reading it, to understand the code. – Harshith Rai Mar 04 '19 at 10:57
0

Simple solution using recursion

function filterElement($element){  
  if(is_array($element[0])){
    return $element[0];
  }
  # base case
  return $element;
}

function permutation($input, $path){  
  // base case 1
  if(count($input) == 0){
    return [$path];
  }

  $output = [];
  foreach($input as $index => $num){     # 1, 2,3, 4
    $copyPath = $path; # copy the path - []
    $copyPath[] = $num;  # append the number [1]

    # remove the current number
    $inputLocal = $input; 
    unset($inputLocal[$index]); # [2, 3, 4]       
    $permute = permutation($inputLocal, $copyPath); # call [2, 3, 4], [1]

    # for all element find add to output
    foreach($permute as $ele){
      # filter ouput
      $output[] = filterElement($ele);    
    }
  }

  return $output;
}


print_r(permutation([1,2,3,4], []));

output

Array
(
    [0] => Array
        (
            [0] => 1
            [1] => 2
            [2] => 3
            [3] => 4
        )

    [1] => Array
        (
            [0] => 1
            [1] => 2
            [2] => 4
            [3] => 3
        )

    [2] => Array
        (
            [0] => 1
            [1] => 3
            [2] => 2
            [3] => 4
        )

    [3] => Array
        (
            [0] => 1
            [1] => 3
            [2] => 4
            [3] => 2
        )

    [4] => Array
        (
            [0] => 1
            [1] => 4
            [2] => 2
            [3] => 3
        )

    [5] => Array
        (
            [0] => 1
            [1] => 4
            [2] => 3
            [3] => 2
        )

    [6] => Array
        (
            [0] => 2
            [1] => 1
            [2] => 3
            [3] => 4
        )

    [7] => Array
        (
            [0] => 2
            [1] => 1
            [2] => 4
            [3] => 3
        )

    [8] => Array
        (
            [0] => 2
            [1] => 3
            [2] => 1
            [3] => 4
        )

    [9] => Array
        (
            [0] => 2
            [1] => 3
            [2] => 4
            [3] => 1
        )

    [10] => Array
        (
            [0] => 2
            [1] => 4
            [2] => 1
            [3] => 3
        )

    [11] => Array
        (
            [0] => 2
            [1] => 4
            [2] => 3
            [3] => 1
        )

    [12] => Array
        (
            [0] => 3
            [1] => 1
            [2] => 2
            [3] => 4
        )

    [13] => Array
        (
            [0] => 3
            [1] => 1
            [2] => 4
            [3] => 2
        )

    [14] => Array
        (
            [0] => 3
            [1] => 2
            [2] => 1
            [3] => 4
        )

    [15] => Array
        (
            [0] => 3
            [1] => 2
            [2] => 4
            [3] => 1
        )

    [16] => Array
        (
            [0] => 3
            [1] => 4
            [2] => 1
            [3] => 2
        )

    [17] => Array
        (
            [0] => 3
            [1] => 4
            [2] => 2
            [3] => 1
        )

    [18] => Array
        (
            [0] => 4
            [1] => 1
            [2] => 2
            [3] => 3
        )

    [19] => Array
        (
            [0] => 4
            [1] => 1
            [2] => 3
            [3] => 2
        )

    [20] => Array
        (
            [0] => 4
            [1] => 2
            [2] => 1
            [3] => 3
        )

    [21] => Array
        (
            [0] => 4
            [1] => 2
            [2] => 3
            [3] => 1
        )

    [22] => Array
        (
            [0] => 4
            [1] => 3
            [2] => 1
            [3] => 2
        )

    [23] => Array
        (
            [0] => 4
            [1] => 3
            [2] => 2
            [3] => 1
        )

)
Faiz Rasool
  • 1,379
  • 1
  • 12
  • 20