Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Deep (infinite) NESTED split words using regex

IMPORTANT EDIT: Since many people said that this should be avoided and almost unable to do using RegEx, I'm going to allow you for some other solutions. As from now on, any solution could be used as an answer and finally a solution. Thanks!

Lets say I have:

$line = "{ It is { raining { and streets are wet } | snowing { and streets are { slippy | white }}}. Tomorrow will be nice { weather | walk }. }" 

Desired output:

It is raining and streets are wet. Tomorrow will be nice weather.
It is raining and streets are wet. Tomorrow will be nice walk.
It is snowing and streets are slippy. Tomorrow will be nice weather.
It is snowing and streets are slippy. Tomorrow will be nice walk.
It is snowing and streets are white. Tomorrow will be nice weather.
It is snowing and streets are white. Tomorrow will be nice walk. 

With the code from this answer to my previous question, I'm currently able to split the words but can't figure out the nested values. Could someone help me out with what I have bellow. I'm pretty sure I should implement a for loop somewhere to make it work but I can't understand where.

$line = "{This is my {sentence|statement} I {wrote|typed} on a {hot|cold} {day|night}.}";
 $matches = getMatches($line);
 printWords([], $matches, $line);


function getMatches(&$line) {
    $line = trim($line, '{}'); 
    $matches = null;
    $pattern = '/\{[^}]+\}/';

    preg_match_all($pattern, $line, $matches);

    $matches = $matches[0];

    $line = preg_replace($pattern, '%s', $line);

    foreach ($matches as $index => $match) {
        $matches[$index] = explode('|', trim($match, '{}'));
    }

    return $matches;
}


function printWords(array $args, array $matches, $line) {
    $current = array_shift($matches);
    $currentArgIndex = count($args);

    foreach ($current as $word) {
        $args[$currentArgIndex] = $word;

        if (!empty($matches)) {
                printWords($args, $matches, $line);
        } else {
                echo vsprintf($line, $args) . '<br />';
        }
    }
}

One way I got on my mind is using lexer technique, as in read char by char, create appropriate bytecodes and then loop through it. It's not regex but it should work.

like image 686
J. Doe Avatar asked Apr 04 '16 21:04

J. Doe


1 Answers

This class does the work, allthough not sure how efficient it is:

class Randomizer {

    public function process($text) {
        return preg_replace_callback('/\{(((?>[^\{\}]+)|(?R))*)\}/x', array($this, 'replace'), $text);
    }

    public function replace($text) {
        $text = $this->process($text[1]);
        $parts = explode('|', $text);
        $part = $parts[array_rand($parts)];
        return $part;
    }
}

To use it you can simply do:

$line = "{This is my {sentence|statement} I {wrote|typed} on a {hot|cold} {day|night}.}";
$randomizer = new Randomizer( );
echo   $randomizer->process($line);

I am not the best when it comes to regular expressions so I can't really explain why that particular regex works, sorry for that.

By the way, it returns random strings and not all the possible strings. Let me know if you need all the strings instead of a random one. I will update the answer..

like image 183
Gogol Avatar answered Oct 07 '22 12:10

Gogol