Replace text within brackets with thus-named varia

2019-02-20 03:43发布

问题:

I want to replace all strings in square brackets ([]) with a randomly chosen item from an array that's named that string.

It's very similar to this issue, but with a twist, in that I want to replace different brackets' contents with strings from arrays named that.

An example should make this a bit clearer.

So say I've got the string

"This is a very [adjective] [noun], and this is a [adjective] [noun]."

And the variables:

$adjective = array("big","small","good","bad");
$noun      = array("house","dog","car");

And we want it to return "This is a very big house, and this is a good dog." or whatever, by choosing randomly. That is, I want to write a PHP function that will replace each [string] with a randomly chosen item from the array named $string. For now it doesn't matter if by randomly choosing it ends up repeating choices, but it must make a fresh choice for each [] item.

I hope I've explained this clearly. If you get what I'm trying to achieve and can think of a better way to do it I'd be very grateful.

回答1:

Algorithm

  1. Match for this regex: (\[.*?\])
  2. For each match group pick an item from the related array.
  3. Replace in string by order.

Implementation

$string    = "This is a very [adjective] [noun], and this is a [adjective] [noun].";
$adjective = array("big","small","good","bad");
$noun      = array("house","dog","car");

// find matches against the regex and replaces them the callback function.
$result    = preg_replace_callback(

                 // Matches parts to be replaced: '[adjective]', '[noun]'
                 '/(\[.*?\])/',

                 // Callback function. Use 'use()' or define arrays as 'global'
                 function($matches) use ($adjective, $noun) {

                     // Remove square brackets from the match
                     // then use it as variable name
                     $array = ${trim($matches[1],"[]")};

                     // Pick an item from the related array whichever.
                     return $array[array_rand($array)];
                 },

                 // Input string to search in.
                 $string
             );

print $result;

Explanation

preg_replace_callback function performs a regular expression search and replace using provided callback function.

  • First parameter is regular expression to match (enclosed between slashes): /(\[.*?\])/

  • Second parameter is callback function to call for each match. Takes the current match as parameter.

    • We have to use use() here to access the arrays from inside the function, or define the arrays as global: global $adjective = .... Namely, we have to do one of the followings:

      a) Define arrays as global:

        ...
        global $adjective = array("big","small","good","bad");
        global $noun      = array("house","dog","car");
        ...
        function($matches) {
        ...
      

      b) Use use:

        ...
        $adjective = array("big","small","good","bad");
        $noun      = array("house","dog","car");
        ...
        function($matches) use ($adjective, $noun) {
        ...
      
    • First line of the callback function:

      • trim: Removes square brackets ([]) from the match using trim function.

      • ${}: Creates a variable to use as array name with the match name. For example, if the $match is [noun] then trim($matches[1],"[]") returns noun (without brackets) and ${noun} becomes the array name: $noun. For more information on the topic, see variable variables.

    • Second line randomly picks an index number available for the $array and then returns the element at this position.

  • Third parameter is the input string.



回答2:

The code below will do the work:

$string = "This is a very [adjective] [noun], and this is a [adjective] [noun]."

function replace_word ( $matches )
{
    $replaces = array(
        '[adjective]'  =>  array("big", "small", "good", "bad"),
        '[noun]'  =>  array("house", "dog", "car")
    );

    return $replaces[$matches[0]][array_rand($replaces[ $matches[0] ])];
}

echo preg_replace_callback("(\[.*?\])", "replace_word", $string);

First, we regular expression match on the [something] parts of the word, and call the replace_word() callback function on it with preg_replace_callback(). This function has an internal $replaces two dimension deep array defined inside, each row defined in a [word type] => array('rep1', 'rep2', ...) format.

The tricky and a bit obfuscated line is the return $replaces[$matches[0]][array_rand($replaces[ $matches[0] ])];. If I chunk it down a bit, it'll be a lot more parsable for you:

$random = array_rand( $replaces[ $matches[0] ] );

$matches[0] is the word type, this is the key in the $replaces array we are searching for. This was found by regular expression in the original string. array_rand() basically selects one element of the array, and returns its numerical index. So $random right now is an integer somewhere between 0 and the (number of elements - 1) of the array containing the replaces.

return $replaces[ $matches[0] ][$random];

This will return the $randomth element from the replace array. In the code snippet, these two lines are put together into one line.

Showing one element only once

If you want disjunct elements (no two adjective or noun repeated twice), then you will need to do another trick. We will set the $replaces array to be defined not inside the replace_word() function, but outside it.

$GLOBALS['replaces'] = array(
    '[adjective]'  =>  array("big", "small", "good", "bad"),
    '[noun]'  =>  array("house", "dog", "car")
);

Inside the function, we will set the local $replaces variable to be a reference to the newly set array, with calling $replaces = &$GLOBALS['replaces'];. (The & operator sets it a reference, so everything we do with $replaces (remove and add elements, for example) modifies the original array too. Without it, it would only be a copy.)

And before arriving on the return line, we call unset() on the currently to-be-returned key.

unset($replaces[$matches[0]][array_rand($replaces[ $matches[0] ])]);

The function put together now looks like this:

function replace_word ( $matches )
{
    $replaces = &$GLOBALS['replaces'];
    unset($replaces[$matches[0]][array_rand($replaces[ $matches[0] ])]);

    return $replaces[$matches[0]][array_rand($replaces[ $matches[0] ])];
}

And because $replaces is a reference to the global, the unset() updates the original array too. The next calling of replace_word() will not find the same replace again.

Be careful with the size of the array!

Strings containing more replace variables than the amount of replace values present will throw an Undefined index E_NOTICE. The following string won't work:

$string = "This is a very [adjective] [noun], and this is a [adjective] [noun]. This is also an [adjective] [noun] with an [adjective] [noun].";

One of the outputs look like the following, showing that we ran out of possible replaces:

This is a very big house, and this is a big house. This is also an small with an .



回答3:

Another good (easier) method of doing this (not my solution)

https://stackoverflow.com/a/15773754/2183699

Using a foreach to check on which variables you want to replace and replacing them with

str_replace();


回答4:

You can use preg_match and str_replace function to achive this goal.

  • First find the matches using preg_match function and then create search & replace array from the result.
  • Call str_replace function by passing the previous arrays as parameters.


回答5:

This is my minor update to mmdemirbas' answer above. It lets you set the variables outside of the function (i.e. use globals, as said).

$result    = preg_replace_callback(

                 // Matches parts to be replaced: '[adjective]', '[noun]'
                 '/(\[.*?\])/',

                 // Callback function. Use 'use()' or define arrays as 'global'
                 function($matches) use ($adjective, $noun) {

                     // Remove square brackets from the match
                     // then use it as variable name
                    $arrayname = trim($matches[1],"[]");
                    $array = $GLOBALS[$arrayname];

                     // Pick an item from the related array whichever.
                     return $array[array_rand($array)];
                 },

                 // Input string to search in.
                 $string
             );

print $result;