Vasilii pricv
Vasilii pricv

Reputation: 13

Reverse the position of all letters in each word of a string that may contain multibyte characters

I want to write an application that reverses all the words of input text, but all non-letter symbols should stay in the same places.

What I already have:

function reverse($string)
{
    $reversedString = '';

    for ($position = strlen($string); $position > 0; $position--) {
        $reversedString .= $string[$position - 1]; //.= - concatenation assignment, привязывает b to a;
      
    }
    return $reversedString;
}

$name = 'ab1 ab2';
print_r(reverse($name)); //output: 2ba 1ba;

Now I want to add for this function some conclusion, that this function also will reverse text, but without affecting any special characters? It means, that all non-letter symbols should stay in the same places.

Here are some sample input strings and my desired output:

My actual project will be using cyrillic characters, so answers need to accommodate multibyte/unicode letters.

Maybe I should use array and '''ctype_alpha''' function?

Upvotes: 0

Views: 620

Answers (2)

mickmackusa
mickmackusa

Reputation: 47894

In an attempt to optimize for performance by reducing total function calls and reducing the number of preg_ calls, I'll demonstrate a technique with nested loops.

  1. Explode on spaces
  2. Separate letters from non-letters while accommodating multi-byte characters
  3. Iterate the matches array which contains 1 character per row (separated so that letters (movable characters) are in the [1] element and non-letters (immovable characters are in the [2] element.
  4. While traversing from left to right along the array of characters, if an immovable character is encountered, immediately append it to the current output string; if movable, seek out the latest unused movable character from the end of the matches array.

Code: (Demo) (variation without isset() calls)

$names = [
    'ab1 ab2', // becomes ba1 ba2
    'qwerty uçop', // becomes ytrewq poçu
    'q1werty% uio*pl', // becomes y1trewq% lpo*iu
    'Привет, мир!', // becomes тевирП, рим!
    'Hello, dear @user_non-name, congrats100 points*@!', // olleH, raed @eman_non-resu, stragnoc100 stniop*@!
    'a' // remains a
];

function swapLetterPositions($string): string {
    $result = [];
    foreach (explode(' ', $string) as $index => $word) {
        $result[$index] = '';
        $count = preg_match_all('/(\pL)|(.)/u', $word, $m, PREG_SET_ORDER);
        for ($i = 0, $j = $count; $i < $count; ++$i) {
            if (isset($m[$i][2])) { // immovable
                $result[$index] .= $m[$i][2]; // append to string
            } else {  // movable from front
                while (--$j >= 0) { // decrement $j and ensure that it represents an element index
                    if (!isset($m[$j][2])) { // movable from back
                        $result[$index] .= $m[$j][1]; // append to string
                        break;
                    }
                }
            }
        }
    }
    return implode(' ', $result);
}

foreach ($names as $name) {
    echo "\"$name\" => \"" . swapLetterPositions($name) . "\"\n---\n";
}

Output:

"ab1 ab2" => "ba1 ba2"
---
"qwerty uçop" => "ytrewq poçu"
---
"q1werty% uio*pl" => "y1trewq% lpo*iu"
---
"Привет, мир!" => "тевирП, рим!"
---
"Hello, dear @user_non-name, congrats100 points*@!" => "olleH, raed @eman_non-resu, stargnoc100 stniop*@!"
---
"a" => "a"
---

Upvotes: 0

Oleg Barabanov
Oleg Barabanov

Reputation: 2764

If I understood your problem correctly, then the solution below will probably be able to help you. This solution is not neat and not optimal, but it seems to work:

//mb_internal_encoding('UTF-8') && mb_regex_encoding('UTF-8'); // <-- if you need

function reverse(string $string): string
{
    $reversedStrings = explode(' ', $string);
    $patternRegexp = '^[a-zA-Zа-яА-Я]+$';

    foreach ($reversedStrings as &$word) {
        $chars = mb_str_split($word, 1);
        $filteredChars = [];
        foreach (array_reverse($chars) as $char) {
            if (mb_ereg_match($patternRegexp, $char)) {
                $filteredChars[] = $char;
            }
        }

        foreach ($chars as &$char) {
            if (!mb_ereg_match($patternRegexp, $char)) {
                continue;
            }
            $char = array_shift($filteredChars);
        }
        $word = implode('', $chars);
    }

    return implode(' ', $reversedStrings);
}
    
$test1 = 'ab1 ab2 ab! ab. Hello!789World! qwe4rty5';
print_r(reverse($test1)."\n"); // => "ba1 ba2 ba! ba. dlroW!789olleH! ytr4ewq5";

$test2 = 'Привет, мир!';
print_r(reverse($test2)."\n"); // => "тевирП, рим!";

In the example, non-alphabetic characters do not change their position within the word. Example: "Hello!789World! qwe4rty5" => "dlroW!789olleH! ytr4ewq5".

Upvotes: 1

Related Questions