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...

mickmackusa
49.1k13 gold badges97 silver badges163 bronze badges
asked Nov 13, 2010 at 10:22

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.

answered Nov 13, 2010 at 12:20

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

answered Nov 13, 2010 at 11:53

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.

answered Nov 13, 2010 at 10:59

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);
 }
}
answered Nov 13, 2010 at 10:36

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);
?>
answered Nov 13, 2010 at 10:58

Comments

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

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.