0

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:

  • ab1 ab2 becomes ba1 ba2
  • qwerty uiop becomes ytrewq poiu
  • q1werty% uio*pl becomes y1trewq% lpo*iu
  • Привет, мир! becomes тевирП, рим!
  • Hello, dear @user_non-name, congrats100 points*@! becomes olleH, raed @eman_non-resu, stragnoc100 stniop*@!

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?

mickmackusa
  • 43,625
  • 12
  • 83
  • 136
  • 1
    What about ab1baa? Does it become aa1bba? – Torbjörn Stabo Dec 26 '21 at 18:47
  • Yes, it should become like in your example – Vasilii pricv Dec 27 '21 at 22:35
  • 1
    No, sorry, I've made mistake. ab1bba should become abb1ba – Vasilii pricv Dec 28 '21 at 08:18
  • Your most recent comment makes no sense since the non-alphabetic character "1" has changed positions from *position 2* to *position 3*. As @mickmackusa commented, it would be nice if you [edited your question](https://stackoverflow.com/posts/70488925/edit) and provided more sample inputs and desired outputs. – steven7mwesigwa Dec 28 '21 at 08:43
  • 1
    Please clarify why your requirements are not satisfied by https://stackoverflow.com/q/2977556/2943403 and https://stackoverflow.com/q/21811792/2943403 and https://stackoverflow.com/q/1169969/2943403 – mickmackusa Dec 28 '21 at 09:03

2 Answers2

1

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

Oleg Barabanov
  • 2,468
  • 2
  • 8
  • 17
  • Thank you! That what I need, but it does not work... – Vasilii pricv Dec 27 '21 at 22:26
  • @Vasiliipricv, Please specify what exactly is not working for you. You can see a working [example online here](https://sandbox.onlinephpfunctions.com/code/8dcb0a79b4dd29aa2f66fd413bd627750d9e1117). – Oleg Barabanov Dec 27 '21 at 22:50
  • @Vasiliipricv, Perhaps you may have a problem with the [mb_str_split() function](https://www.php.net/manual/ru/function.mb-str-split.php), because it is supported in php version >= 7.4.0. If you have PHP version < 7.4.0, [here in the comments on php.net](https://www.php.net/manual/ru/function.mb-str-split.php#125429) the polyfill is presented. – Oleg Barabanov Dec 27 '21 at 22:59
  • Thank you! It's working on the site, but don't wotking in my Phpstorm( – Vasilii pricv Dec 28 '21 at 08:40
  • This answer is correct until multibyte characters are introduced. Please see Vasilii's updated battery of test strings and desired output. https://3v4l.org/kk9Ve – mickmackusa Jan 01 '22 at 14:25
  • 1
    @mickmackusa, thank you for attention. I really don't know if the problem is still relevant, but I corrected the example by doing a search in multibyte strings using `mb_ereg_match()`. [Example here](http://sandbox.onlinephpfunctions.com/code/fe8d2c747d55ea081cc8a1bd12f1c002100bf497) – Oleg Barabanov Jan 02 '22 at 02:01
0

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"
---
mickmackusa
  • 43,625
  • 12
  • 83
  • 136