How to compute the cartesian power of a range of characters? - php

I would like to make a function that is able to generate a list of letters and optional numbers using a-z,0-9.
$output = array();
foreach(range('a','z') as $i) {
foreach(range('a','z') as $j) {
foreach(range('a','z') as $k) {
$output[] =$i.$j.$k;
}
}
}
Thanks
example:
myfunction($include, $length)
usage something like this:
myfunction('a..z,0..9', 3);
output:
000
001
...
aaa
aab
...
zzz
The output would have every possible combination of the letters, and numbers.

Setting the stage
First, a function that expands strings like "0..9" to "0123456789" using range:
function expand_pattern($pattern) {
$bias = 0;
$flags = PREG_SET_ORDER | PREG_OFFSET_CAPTURE;
preg_match_all('/(.)\.\.(.)/', $pattern, $matches, $flags);
foreach ($matches as $match) {
$range = implode('', range($match[1][0], $match[2][0]));
$pattern = substr_replace(
$pattern,
$range,
$bias + $match[1][1],
$match[2][1] - $match[1][1] + 1);
$bias += strlen($range) - 4; // 4 == length of "X..Y"
}
return $pattern;
}
It handles any number of expandable patterns and takes care to preserve their position inside your source string, so for example
expand_pattern('abc0..4def5..9')
will return "abc01234def56789".
Calculating the result all at once
Now that we can do this expansion easily, here's a function that calculates cartesian products given a string of allowed characters and a length:
function cartesian($pattern, $length) {
$choices = strlen($pattern);
$indexes = array_fill(0, $length, 0);
$results = array();
$resets = 0;
while ($resets != $length) {
$result = '';
for ($i = 0; $i < $length; ++$i) {
$result .= $pattern[$indexes[$i]];
}
$results[] = $result;
$resets = 0;
for ($i = $length - 1; $i >= 0 && ++$indexes[$i] == $choices; --$i) {
$indexes[$i] = 0;
++$resets;
}
}
return $results;
}
So for example, to get the output described in the question you would do
$options = cartesian(expand_pattern('a..z0..9'), 3);
See it in action (I limited the expansion length to 2 so that the output doesn't explode).
Generating the result on the fly
Since the result set can be extremely large (it grows exponentially with $length), producing it all at once can turn out to be prohibitive. In that case it is possible to rewrite the code so that it returns each value in turn (iterator-style), which has become super easy with PHP 5.5 because of generators:
function cartesian($pattern, $length) {
$choices = strlen($pattern);
$indexes = array_fill(0, $length, 0);
$resets = 0;
while ($resets != $length) {
$result = '';
for ($i = 0; $i < $length; ++$i) {
$result .= $pattern[$indexes[$i]];
}
yield $result;
$resets = 0;
for ($i = $length - 1; $i >= 0 && ++$indexes[$i] == $choices; --$i) {
$indexes[$i] = 0;
++$resets;
}
}
}
See it in action.

See this answer for a code that produces all possible combinations:
https://stackoverflow.com/a/8567199/1800369
You just need to add the $length parameter to limit the combinations size.

You can use a recursive function
assuming you mean it can be any number of levels deep, you can use a recursive function to generate an array of the permutations e.g.:
/**
* take the range of characters, and generate an array of all permutations
*
* #param array $range range of characters to itterate over
* #param array $array input array - operated on by reference
* #param int $depth how many chars to put in the resultant array should be
* #param int $currentDepth internal variable to track how nested the current call is
* #param string $prefix internal variable to know what to prefix the current string with
* #return array permutations
*/
function foo($range, &$array, $depth = 1, $currentDepth = 0, $prefix = "") {
$start = !$currentDepth;
$currentDepth++;
if ($currentDepth > $depth) {
return;
}
foreach($range as $char) {
if ($currentDepth === $depth) {
$array[] = $prefix . $char;
continue;
}
foo($range, $array, $depth, $currentDepth, $prefix . $char);
}
if ($start) {
return $array;
}
With the above function, initialize the return variable and call it:
$return = array();
echo implode(foo(range('a', 'z'), $return, 3), "\n");
And you're output will be all three char combinations from aaa, to zzz:
aaa
aab
...
zzy
zzz
The numeric parameter determins how recursive the function is:
$return = array();
echo implode(foo(range('a', 'z'), $return, 1), "\n");
a
b
c
...
Here's a live example.

$number= range(0, 9);
$letters = range('a', 'z');
$array= array_merge($number, $letters);
//print_r($array);
for($a=0;$a<count($array);$a++){
for($b=0;$b<count($array);$b++){
for($c=0;$c<count($array);$c++){
echo $array[$a].$array[$b].$array[$c]."<br>";
}
}
}
tested and working :)

Related

Return the number of all unique case-insensitive characters

I am trying create a function which returns the number of all unique case-insensitive
characters that occur >= $n times in a given string.
For example:
function getNumOfUniqueCharacters($str, $n) {
// ...
}
getNumOfUniqueCharacters('A1B2C3', 2); // 0
getNumOfUniqueCharacters('A1a1C1', 2);
// 2, because A and 1 both occur 2 or more times.
getNumOfUniqueCharacters('Alabama', 3); // 1
I did this:
function getNumOfUniqueCharacters($text)
{
$ret = 0;
$a = [];
$t = str_split(strtolower($text));
$l = count($t);
for ($i = 0; $i < $l; $i++) {
$c = $t[$i];
if (array_key_exists($c, $t)) {
if ($t[$c] === 1)
$ret += 1;
$t[$c] += 1;
} else {
$t[$c] = 1;
}
}
return $ret;
}
But it does not work so good, I need to add second argument $n.
How to add it correctly?
I hope I got your question right.
Here's my idea for this code:
<?php
$string = "A1B2C1A2b2b4b5";
function getNumOfUniqueCharacters($string, $n)
{
$occurrenceArray = array();
$text = str_split(strtolower($string));
//put each character in a keyValue array and count them
foreach($text as $character){
if(!array_key_exists($character, $occurrenceArray)) $occurrenceArray[$character] = 1;
else $occurrenceArray[$character]++;
}
//loop through keyValue array and remove everything that has value < $n
foreach($occurrenceArray as $key => $value)
{
if($value < $n) unset($occurrenceArray[$key]);
}
//return array
return $occurrenceArray;
}
print_r(getNumOfUniqueCharacters($string, 2));
This code right here will print the following:
Array (
[a] => 2
[1] => 2
[b] => 4
[2] => 3 )
Edit: If you need the count of how many characters repeat more than $n, you can simply replace the return with return count($occurrenceArray);
This task is pretty easy, if you use array functions of PHP:
function getNumOfUniqueCharacters(string $string = '', int $n = 1): int {
// Split the string by character and count the occurences of all values
$counted = array_count_values(mb_str_split(mb_strtolower($str)));
// Discard everything, that is does not match the $n parameter
$counted = array_filter($counted, function($a) use($n) {
return $a >= $n;
});
// Return the length of the remaining array
return count($counted);
}
Also note, that you may use mb_* functions, so your code will work with multibyte characters.
I have written you a function with a lot of comments to explain the thought process,
function getNumOfUniqueCharacters($string, $n = null) {
// Map all case-insensitive characters to an array
$map = str_split(strtolower($string), 1);
// Character registry
$reg = array_count_values($map);
// Filter out single occurances
$reg = array_filter($reg, function($v){return $v > 1;});
// Filter out less than $n occurances (if $n is not null)
if (null !== $n) {
$reg = array_filter($reg, function($v)use($n){return $v >= $n;});
}
// Return the number duplicate occurances (or more than n occurances)
return count($reg);
}
Usage:
echo getNumOfUniqueCharacters('A1B2C3', 2) . PHP_EOL;
echo getNumOfUniqueCharacters('A1a1C1', 2) . PHP_EOL;
echo getNumOfUniqueCharacters('Alabama', 3) . PHP_EOL;
echo getNumOfUniqueCharacters('Mississippi') . PHP_EOL;
Output:
0
2
1
3

Convert Excel column coordinates to integer position in PHP

I need to convert Excel coordinates (for example "AD45") into X=30 and Y=45 positions in integers.
I have this snippet of PHP code:
/**
* #param String $coordinates
*
* #return array
*/
public function getCoordinatesPositions($coordinates) {
$letters = preg_replace('/[^a-zA-Z]/', '', $coordinates);
$numbers = preg_replace('/[^0-9]/', '', $coordinates);
$letters = strtoupper($letters);
$columnCoordinate = 0;
$alphabetIterate = 0;
$alphabetRange = range('A', 'Z');
$alphabetCount = count($alphabetRange);
$splittedLetters = str_split($letters);
$lettersCount = count($splittedLetters);
$i = 1;
if ($lettersCount === 1) {
$columnCoordinate = array_search($splittedLetters[0], $alphabetRange) + 1;
} else {
foreach ($splittedLetters as $letter) {
if ($i !== $lettersCount) {
$position = (array_search($letter, $alphabetRange) + 1) * $alphabetCount;
} else {
$position = (array_search($letter, $alphabetRange) + 1);
}
$columnCoordinate += $position;
$i++;
}
}
return array('column' => $columnCoordinate, 'row' => $numbers);
}
My problem is, that this function is not returning correct column value if you pass coordinates with 3 or more letters ("ABC45"). And my colleague said, that this algorithm is also poor performance.
Do you have any ideas for simpler and better performance algorithm? Thank you.
In principle the algorithm is fine. You can simplify it and make it more general this way:
function getCoordinatesPositions($coordinates) {
$letters = preg_replace('/[^a-zA-Z]/', '', $coordinates);
$numbers = preg_replace('/[^0-9]/', '', $coordinates);
$letters = strtoupper($letters);
$alphabetRange = range('A', 'Z');
$alphabetCount = count($alphabetRange);
$splittedLetters = str_split($letters);
$lettersCount = count($splittedLetters);
$columnCoordinate = 0;
$i = 1;
foreach ($splittedLetters as $letter) {
$columnCoordinate += (array_search($letter, $alphabetRange) + 1) * pow($alphabetCount, $lettersCount - $i);
$i++;
}
return array('column' => $columnCoordinate, 'row' => intval($numbers));
}
var_dump(getCoordinatesPositions("ABC456"));
For PHPExcel see PHPExcel how to get column index from cell.
The #Axel Richter's answer is a good solution and works fine, but it may be improved to:
Secure against wrong coordinates.
Reduce code.
And probably increase performance.
Here is the proposed version:
function getCoordinatesPositions($coordinates) {
if (preg_match('/^([a-z]+)(\d+)$/i', $coordinates, $matches)) {
$level = strlen($matches[1]);
$matches[1] = array_reduce(
str_split(strtoupper($matches[1])),
function($result, $letter) use (&$level) {
return $result + (ord($letter) - 64) * pow(26, --$level);
}
);
return array_splice($matches, 1);
}
// (returns NULL when wrong $coordinates)
}
Using the initial preg_match() ensures to avoid working with wrong coordinates, and directly extracts the column part into $matches['1'].
Now the main improvement is to use ord($letter) to compute the letter's individual value: it avoids creating a temporary array of range('A', 'Z'), and simplifies the evaluation.
Then array_reduce() allows more compact processing of the column part, which is modified in situ, so the final return is also simplified as a simple part of the intermediary $matches.

keyword highlight is highlighting the highlights in PHP preg_replace()

I have a small search engine doing its thing, and want to highlight the results. I thought I had it all worked out till a set of keywords I used today blew it out of the water.
The issue is that preg_replace() is looping through the replacements, and later replacements are replacing the text I inserted into previous ones. Confused? Here is my pseudo function:
public function highlightKeywords ($data, $keywords = array()) {
$find = array();
$replace = array();
$begin = "<span class=\"keywordHighlight\">";
$end = "</span>";
foreach ($keywords as $kw) {
$find[] = '/' . str_replace("/", "\/", $kw) . '/iu';
$replace[] = $begin . "\$0" . $end;
}
return preg_replace($find, $replace, $data);
}
OK, so it works when searching for "fred" and "dagg" but sadly, when searching for "class" and "lass" and "as" it strikes a real issue when highlighting "Joseph's Class Group"
Joseph's <span class="keywordHighlight">Cl</span><span <span c<span <span class="keywordHighlight">cl</span>ass="keywordHighlight">lass</span>="keywordHighlight">c<span <span class="keywordHighlight">cl</span>ass="keywordHighlight">lass</span></span>="keywordHighlight">ass</span> Group
How would I get the latter replacements to only work on the non-HTML components, but to also allow the tagging of the whole match? e.g. if I was searching for "cla" and "lass" I would want "class" to be highlighted in full as both the search terms are in it, even though they overlap, and the highlighting that was applied to the first match has "class" in it, but that shouldn't be highlighted.
Sigh.
I would rather use a PHP solution than a jQuery (or any client-side) one.
Note: I have tried to sort the keywords by length, doing the long ones first, but that means the cross-over searches do not highlight, meaning with "cla" and "lass" only part of the word "class" would highlight, and it still murdered the replacement tags :(
EDIT: I have messed about, starting with pencil & paper, and wild ramblings, and come up with some very unglamorous code to solve this issue. It's not great, so suggestions to trim/speed this up would still be greatly appreciated :)
public function highlightKeywords ($data, $keywords = array()) {
$find = array();
$replace = array();
$begin = "<span class=\"keywordHighlight\">";
$end = "</span>";
$hits = array();
foreach ($keywords as $kw) {
$offset = 0;
while (($pos = stripos($data, $kw, $offset)) !== false) {
$hits[] = array($pos, $pos + strlen($kw));
$offset = $pos + 1;
}
}
if ($hits) {
usort($hits, function($a, $b) {
if ($a[0] == $b[0]) {
return 0;
}
return ($a[0] < $b[0]) ? -1 : 1;
});
$thisthat = array(0 => $begin, 1 => $end);
for ($i = 0; $i < count($hits); $i++) {
foreach ($thisthat as $key => $val) {
$pos = $hits[$i][$key];
$data = substr($data, 0, $pos) . $val . substr($data, $pos);
for ($j = 0; $j < count($hits); $j++) {
if ($hits[$j][0] >= $pos) {
$hits[$j][0] += strlen($val);
}
if ($hits[$j][1] >= $pos) {
$hits[$j][1] += strlen($val);
}
}
}
}
}
return $data;
}
I've used the following to address this problem:
<?php
$protected_matches = array();
function protect(&$matches) {
global $protected_matches;
return "\0" . array_push($protected_matches, $matches[0]) . "\0";
}
function restore(&$matches) {
global $protected_matches;
return '<span class="keywordHighlight">' .
$protected_matches[$matches[1] - 1] . '</span>';
}
preg_replace_callback('/\x0(\d+)\x0/', 'restore',
preg_replace_callback($patterns, 'protect', $target_string));
The first preg_replace_callback pulls out all matches and replaces them with nul-byte-wrapped placeholders; the second pass replaces them with the span tags.
Edit: Forgot to mention that $patterns was sorted by string length, longest to shortest.
Edit; another solution
<?php
function highlightKeywords($data, $keywords = array(),
$prefix = '<span class="hilite">', $suffix = '</span>') {
$datacopy = strtolower($data);
$keywords = array_map('strtolower', $keywords);
$start = array();
$end = array();
foreach ($keywords as $keyword) {
$offset = 0;
$length = strlen($keyword);
while (($pos = strpos($datacopy, $keyword, $offset)) !== false) {
$start[] = $pos;
$end[] = $offset = $pos + $length;
}
}
if (!count($start)) return $data;
sort($start);
sort($end);
// Merge and sort start/end using negative values to identify endpoints
$zipper = array();
$i = 0;
$n = count($end);
while ($i < $n)
$zipper[] = count($start) && $start[0] <= $end[$i]
? array_shift($start)
: -$end[$i++];
// EXAMPLE:
// [ 9, 10, -14, -14, 81, 82, 86, -86, -86, -90, 99, -103 ]
// take 9, discard 10, take -14, take -14, create pair,
// take 81, discard 82, discard 86, take -86, take -86, take -90, create pair
// take 99, take -103, create pair
// result: [9,14], [81,90], [99,103]
// Generate non-overlapping start/end pairs
$a = array_shift($zipper);
$z = $x = null;
while ($x = array_shift($zipper)) {
if ($x < 0)
$z = $x;
else if ($z) {
$spans[] = array($a, -$z);
$a = $x;
$z = null;
}
}
$spans[] = array($a, -$z);
// Insert the prefix/suffix in the start/end locations
$n = count($spans);
while ($n--)
$data = substr($data, 0, $spans[$n][0])
. $prefix
. substr($data, $spans[$n][0], $spans[$n][1] - $spans[$n][0])
. $suffix
. substr($data, $spans[$n][1]);
return $data;
}
I had to revisit this subject myself today and wrote a better version of the above. I'll include it here. It's the same idea only easier to read and should perform better since it uses arrays instead of concatenation.
<?php
function highlight_range_sort($a, $b) {
$A = abs($a);
$B = abs($b);
if ($A == $B)
return $a < $b ? 1 : 0;
else
return $A < $B ? -1 : 1;
}
function highlightKeywords($data, $keywords = array(),
$prefix = '<span class="highlight">', $suffix = '</span>') {
$datacopy = strtolower($data);
$keywords = array_map('strtolower', $keywords);
// this will contain offset ranges to be highlighted
// positive offset indicates start
// negative offset indicates end
$ranges = array();
// find start/end offsets for each keyword
foreach ($keywords as $keyword) {
$offset = 0;
$length = strlen($keyword);
while (($pos = strpos($datacopy, $keyword, $offset)) !== false) {
$ranges[] = $pos;
$ranges[] = -($offset = $pos + $length);
}
}
if (!count($ranges))
return $data;
// sort offsets by abs(), positive
usort($ranges, 'highlight_range_sort');
// combine overlapping ranges by keeping lesser
// positive and negative numbers
$i = 0;
while ($i < count($ranges) - 1) {
if ($ranges[$i] < 0) {
if ($ranges[$i + 1] < 0)
array_splice($ranges, $i, 1);
else
$i++;
} else if ($ranges[$i + 1] < 0)
$i++;
else
array_splice($ranges, $i + 1, 1);
}
// create substrings
$ranges[] = strlen($data);
$substrings = array(substr($data, 0, $ranges[0]));
for ($i = 0, $n = count($ranges) - 1; $i < $n; $i += 2) {
// prefix + highlighted_text + suffix + regular_text
$substrings[] = $prefix;
$substrings[] = substr($data, $ranges[$i], -$ranges[$i + 1] - $ranges[$i]);
$substrings[] = $suffix;
$substrings[] = substr($data, -$ranges[$i + 1], $ranges[$i + 2] + $ranges[$i + 1]);
}
// join and return substrings
return implode('', $substrings);
}
// Example usage:
echo highlightKeywords("This is a test.\n", array("is"), '(', ')');
echo highlightKeywords("Classes are as hard as they say.\n", array("as", "class"), '(', ')');
// Output:
// Th(is) (is) a test.
// (Class)es are (as) hard (as) they say.
OP - something that's not clear in the question is whether $data can contain HTML from the get-go. Can you clarify this?
If $data can contain HTML itself, you are getting into the realms attempting to parse a non-regular language with a regular language parser, and that's not going to work out well.
In such a case, I would suggest loading the $data HTML into a PHP DOMDocument, getting hold of all of the textNodes and running one of the other perfectly good answers on the contents of each text block in turn.

PHP functions question

I'm fairly new to PHP functions I really dont know what the bottom functions do, can some one give an explanation or working example explaining the functions below. Thanks.
PHP functions.
function mbStringToArray ($str) {
if (empty($str)) return false;
$len = mb_strlen($str);
$array = array();
for ($i = 0; $i < $len; $i++) {
$array[] = mb_substr($str, $i, 1);
}
return $array;
}
function mb_chunk_split($str, $len, $glue) {
if (empty($str)) return false;
$array = mbStringToArray ($str);
$n = 0;
$new = '';
foreach ($array as $char) {
if ($n < $len) $new .= $char;
elseif ($n == $len) {
$new .= $glue . $char;
$n = 0;
}
$n++;
}
return $new;
}
The first function takes a multibyte string and converts it into an array of characters, returning the array.
The second function takes a multibyte string and inserts the $glue string every $len characters.
function mbStringToArray ($str) { // $str is a function argument
if (empty($str)) return false; // empty() checks if the argument is not equal to NULL (but does exist)
$len = mb_strlen($str); // returns the length of a multibyte string (ie UTF-8)
$array = array(); // init of an array
for ($i = 0; $i < $len; $i++) { // self explanatory
$array[] = mb_substr($str, $i, 1); // mb_substr() substitutes from $str one char for each pass
}
return $array; // returns the result as an array
}
That should help you to understand the second function

How to generate all permutations of a string in PHP?

I need an algorithm that return all possible combination of all characters in one string.
I've tried:
$langd = strlen($input);
for($i = 0;$i < $langd; $i++){
$tempStrang = NULL;
$tempStrang .= substr($input, $i, 1);
for($j = $i+1, $k=0; $k < $langd; $k++, $j++){
if($j > $langd) $j = 0;
$tempStrang .= substr($input, $j, 1);
}
$myarray[] = $tempStrang;
}
But that only returns the same amount combination as the length of the string.
Say the $input = "hey", the result would be: hey, hye, eyh, ehy, yhe, yeh.
You can use a back tracking based approach to systematically generate all the permutations:
// function to generate and print all N! permutations of $str. (N = strlen($str)).
function permute($str,$i,$n) {
if ($i == $n)
print "$str\n";
else {
for ($j = $i; $j < $n; $j++) {
swap($str,$i,$j);
permute($str, $i+1, $n);
swap($str,$i,$j); // backtrack.
}
}
}
// function to swap the char at pos $i and $j of $str.
function swap(&$str,$i,$j) {
$temp = $str[$i];
$str[$i] = $str[$j];
$str[$j] = $temp;
}
$str = "hey";
permute($str,0,strlen($str)); // call the function.
Output:
#php a.php
hey
hye
ehy
eyh
yeh
yhe
My variant (works as well with array or string input)
function permute($arg) {
$array = is_string($arg) ? str_split($arg) : $arg;
if(1 === count($array))
return $array;
$result = array();
foreach($array as $key => $item)
foreach(permute(array_diff_key($array, array($key => $item))) as $p)
$result[] = $item . $p;
return $result;
}
P.S.: Downvoter, please explain your position. This code uses additional str_split and array_diff_key standard functions, but this code snippet is the smallest, it implements pure tail recursion with just one input parameter and it is isomorphic to the input data type.
Maybe it will lose benchmarks a little when comparing with other implementations (but performance is actually almost the same as in #codaddict's answer for several character strings), but why we can't we just consider it as one of the different alternatives which has its own advantages?
I would put all the characters in an array, and write a recursive function that will 'stripe out' all the remaining characters. If the array is empty, to a reference passed array.
<?php
$input = "hey";
function string_getpermutations($prefix, $characters, &$permutations)
{
if (count($characters) == 1)
$permutations[] = $prefix . array_pop($characters);
else
{
for ($i = 0; $i < count($characters); $i++)
{
$tmp = $characters;
unset($tmp[$i]);
string_getpermutations($prefix . $characters[$i], array_values($tmp), $permutations);
}
}
}
$characters = array();
for ($i = 0; $i < strlen($input); $i++)
$characters[] = $input[$i];
$permutations = array();
print_r($characters);
string_getpermutations("", $characters, $permutations);
print_r($permutations);
Prints out:
Array
(
[0] => h
[1] => e
[2] => y
)
Array
(
[0] => hey
[1] => hye
[2] => ehy
[3] => eyh
[4] => yhe
[5] => yeh
)
Ah yes,
combinations = order doens't matter.
permutations = order does matter.
So hey, hye yeh are all the same combination, but 3 separate permutations as mentioned. Watch out that the scale of items goes up very fast. It's called factorial, and is written like 6! = 6*5*4*3*2*1 = 720 items (for a 6 character string). A 10 character string will be 10! = 3628800 permutations already, which is a very big array. In this example it's 3! = 3*2*1 = 6.
My approach uses recursion and no loops, please check and give feedback:
function permute($str,$index=0,$count=0)
{
if($count == strlen($str)-$index)
return;
$str = rotate($str,$index);
if($index==strlen($str)-2)//reached to the end, print it
{
echo $str."<br> ";//or keep it in an array
}
permute($str,$index+1);//rotate its children
permute($str,$index,$count+1);//rotate itself
}
function rotate($str,$index)
{
$tmp = $str[$index];
$i=$index;
for($i=$index+1;$i<strlen($str);$i++)
{
$str[$i-1] = $str[$i];
}
$str[$i-1] = $tmp;
return $str;
}
permute("hey");
I made a simple class that uses Generators to create the permutations.This way you can just iterate over all possible combinations without exhausting the memory.
The class can take either a string or an array,
and returns a Generator object which can be iterated over with foreach.
Obviously the longer the string or array, the longer it takes to generate all the permutations.
This has been build against PHP 7.4
class Permutation {
/** #var string|array **/
protected $permutationRoot;
protected int $permutationLength;
/**
* #param $permutationRoot
*/
protected function __construct( $permutationRoot ) {
$this->permutationRoot = $permutationRoot;
$this->permutationLength = is_array($permutationRoot)
? count($permutationRoot)
: strlen($permutationRoot);
}
/**
* #param string|array $permutationRoot
*
* #return \Generator
*/
public static function resolve( $permutationRoot ): \Generator
{
$instance = new static($permutationRoot);
return $instance->backTrack(
$instance->permutationRoot,
0,
$instance->permutationLength,
);
}
/**
* #param string|array $permutation
* #param int $index
* #param int $length
*
* #return \Generator
*/
protected function backTrack($permutation, int $index, int $length): \Generator
{
if ($index === $length) {
yield $permutation;
}
for ($i = $index; $i < $length; $i++) {
$this->swap($permutation, $index, $i);
yield from $this->backTrack($permutation, $index + 1, $length);
$this->swap($permutation, $index, $i); // backtrack.
}
}
/**
* #param $permutation
* #param int $index
* #param int $n
*
* #return void
*/
protected function swap(&$permutation, int $index, int $n): void {
$temp = $permutation[$index];
$permutation[$index] = $permutation[$n];
$permutation[$n] = $temp;
}
}
// Test
foreach ( Permutation::resolve('hey') as $perm ) {
echo $perm . "\n";
}
$sentence = "This is a cat";
$words = explode(" ", $sentence);
$num_words = count($words);
$uniqueWords = [];
for ($i = 0; $i < $num_words; $i++) {
for ($j = $i; $j < $num_words; $j++) {
$uniqueWord = '';
for ($k = $i; $k <= $j; $k++) {
$uniqueWord .= $words[$k] . ' ';
}
$uniqueWords[] = trim($uniqueWord);
}
}
var_dump($uniqueWords);
This worked for me

Categories