2

For a project I'm working on, I have a base URI with placeholders and I want to generate all the possible combinations from an array of possible values for each placeholder using PHP.

More concretely:

$uri = "foo/bar?foo=%foo%&bar=%bar%";

$placeholders = array(
  '%foo%' => array('a', 'b'),
  '%bar%' => array('c', 'd'),
  // ...
);

I'd like ending up having the following array:

array(4) {
  [0]=>
  string(23) "foo/bar?foo=a&bar=c"
  [1]=>
  string(23) "foo/bar?foo=a&bar=d"
  [2]=>
  string(19) "foo/bar?foo=b&bar=c"
  [3]=>
  string(19) "foo/bar?foo=b&bar=d"
}

Not to mention I should be able to add more placeholders to generate more computed URIs, of course, so the solution should work recursively.

I might be overtired these days, but I'm getting stuck at achieving this simply, and I'm sure there's a simple way, perhaps even with built-in PHP functions…

5 Answers 5

3
$uri= "foo/bar?foo=%foo%&bar=%bar%&baz=%baz%";
$placeholders = array(
    '%foo%' => array('a', 'b'),
    '%bar%' => array('c', 'd', 'e'),
    '%baz%' => array('f', 'g')
    );

//adds a level of depth in the combinations for each new array of values
function expandCombinations($combinations, $values)
{
    $results = array();
    $i=0;
    //combine each existing combination with all the new values
    foreach($combinations as $combination) {
        foreach($values as $value) {
            $results[$i] = is_array($combination) ? $combination : array($combination);
            $results[$i][] = $value;
            $i++;
        }
    }
    return $results;
}   

//generate the combinations
$patterns = array();
foreach($placeholders as $pattern => $values)
{
    $patterns[] = $pattern;
    $combinations = isset($combinations) ? expandCombinations($combinations, $values) : $values;
}

//generate the uris for each combination
foreach($combinations as $combination)
{
    echo str_replace($patterns, $combination, $uri),"\n";
}

The idea here is to list in an array all the possible combinations for the replacements. The function expandCombinations just adds one level of depth in the combinations for each new pattern to replace with no recursion (we know how PHP loves recursion). This should allow for a decent number of patterns to replace without recursing at an insane depth.

Sign up to request clarification or add additional context in comments.

Comments

3
    <?php
    function rec($values,$keys,$index,$str,&$result)
    {
    if($index<count($values))
    foreach($values[$index] as $val)
    rec($values,$keys,$index+1,$str.substr($keys[$index],1,strlen($keys[$index])-2)."=".$val."&",$result);
    else
    $result[count($result)] = $str;
    }



// Now for test


    $placeholders = array(
      '%foo%' => array('a', 'b'),
      '%bar%' => array('c', 'd' , 'h'),
    );
    $xvalues = array_values($placeholders) ;
    $xkeys = array_keys($placeholders) ;
    $result = array();  
    rec($xvalues,$xkeys,0,"",$result);  // calling the recursive function
    print_r($result);
    // the result will be:
    Array ( 
    [0] => foo=a&bar=c& 
    [1] => foo=a&bar=d& 
    [2] => foo=a&bar=h& 
    [3] => foo=b&bar=c& 
    [4] => foo=b&bar=d& 
    [5] => foo=b&bar=h& 
    ) 
    ?>

It handles unlimited count of placeholders & unlimited count of values

1 Comment

Very nice! it only lacked the string replacements from the original URI, but it's a definitely interesting solution.
2

A recursive solution:

function enumerate($uri, $placeholders){
    $insts = array();
    if (!empty($placeholders)){
        $key = array_keys($placeholders)[0];
        $values = array_pop($placeholders);

        foreach($values => $value){
            $inst = str_replace($uri, $key, $value);
            $insts = array_merge($insts, (array)enumerate($inst, $placeholders));
        }
        return $insts;
    } else {
        return $uri;
    }
}

Each call to the function pops one placeholder off the array and loops through its potential values enumerating through all the remaining placeholder values for each one. The complexity is O(k^n) where k is the average number of replacements for each placeholder and n is the number of placeholders.

My PHP is a little rusty; let me know if I got any of the syntax wrong.

1 Comment

Pythonista spotted, right? ;) unfortunately, I spent some times to tweak the code but I didn't manage to achieve making it work even if I think I get the idea…
1
foreach($placeholders['%foo%'] as $foo){
    foreach($placeholders['%bar%'] as $bar){
      $container[] = str_replace(array('%foo%','%bar%'),array($foo,$bar),$uri);
    }
}

5 Comments

Yes sure that works for two levels but that's not really recursion (I should be able to add as much placeholders as I want without embedding foreach loops manually). I edited the question to reflect this need more clearly.
That is not a good approach, imagine you have 10 plceholders and 3 values for each this means 10 embedded loops. It will rise exponentially that mean 3 to the power of 10 which are 50K+ loops
infinity> Yes I know, but then I could have a configuration setting allowing a max recursion level and/or number of placeholders to avoid ending up creating black holes.
My function allows any number of values.
Aurel300> Cool! Where is it? :)
0

This work, but it's not so elegant because of the need to get rid of the placeholder array keys :

<?php

    /*
     * borrowed from http://www.theserverpages.com/php/manual/en/ref.array.php
     * author: skopek at mediatac dot com 
     */
    function array_cartesian_product($arrays) {
        //returned array...
        $cartesic = array();

        //calculate expected size of cartesian array...
        $size = sizeof($arrays) > 0 ? 1 : 0;

        foreach ($arrays as $array) {
            $size *= sizeof($array);
        }

        for ($i = 0; $i < $size; $i++) {
            $cartesic[$i] = array();

            for ($j = 0; $j < sizeof($arrays); $j++) {
                $current = current($arrays[$j]); 
                array_push($cartesic[$i], $current);    
            }

            //set cursor on next element in the arrays, beginning with the last array
            for ($j = sizeof($arrays) - 1; $j >= 0; $j--) {
                //if next returns true, then break
                if (next($arrays[$j])) {
                    break;
                } else { //if next returns false, then reset and go on with previuos array...
                    reset($arrays[$j]);
                }
            }
        }

        return $cartesic;
    }

    $uri = "foo/bar?foo=%foo%&bar=%bar%";
    $placeholders = array(
        0 => array('a', 'b'), // '%foo%'
        1 => array('c', 'd'), // '%bar%'
    );

    //example
    header("Content-type: text/plain");
    $prod = array_cartesian_product($placeholders);
    $result = array();

    foreach ($prod as $vals) {
        $temp = str_replace('%foo%', $vals[0], $uri);
        $temp = str_replace('%bar%', $vals[1], $temp);
        array_push($result, $temp);
    }

    print_r($result);

?>

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.