edd mann

Reversing a String in PHP

Since recently setting up a forum for Three Devs and a Maybe, we have started to partake in a weekly code-kata. What could be more fitting to start with than the common interview question, reversing a string in 'X' language. In this case the language is PHP, and below are some of the many ways contrived to solve the problem.

Basic Implementations

The implementation below is the most simple, taking advantage of PHP's vast amount of 'built-in' functions to reverse the supplied string.

function reverse($str)
{
    return strrev($str);
}

In a similar manner, we are able to compose a 'reverse' function by joining (alias for 'implode') a reversed array of the string characters.

function reverse($str)
{
    return join('', array_reverse(str_split($str)));
}

The most imperative approach to reverse a string is by looping over each character with indexes at each end, swapping their contents upon each iteration. PHP's ability to access individual characters in an array manner turns out to be very useful in this case.

function reverse($str)
{
    for ($i = 0, $j = strlen($str) - 1; $i < $j; $i++, $j--) {
        $tmp = $str[$i];
        $str[$i] = $str[$j];
        $str[$j] = $tmp;
    }

    return $str;
}

Though not best practice, the desired result can be compacted into a single 'for' loop declaration, shown below.

function reverse($str)
{
    for ($i = strlen($str) - 1, $out = ''; $i >= 0; $out .= $str[$i--]) {}

    return $out;
}

Another non-practical approach using the 'array_walk' function can be found below. An interesting implementation detail, is how you are able to clearly see that a copy of '$out' is being passed into the closure function.

function reverse($str)
{
    list($out, $len) = [ str_split($str), strlen($str) - 1 ];

    array_walk($out, function(&$ele, $idx) use ($out, $len) // copy, not ref.
    {
        $ele = $out[$len - $idx];
    });

    return join('', $out);
}

Recursive Implementations

The second group of implementations contrived used forms of recursion to achieve the desired result. The first of such methods is a simple recursive invocation of the function, removing the head character upon each call. Once the string has reached one character, the base-case has been hit and the remaining string is simply returned.

function reverse($str)
{
    if (strlen($str) < 2) {
        return $str;
    }

    return reverse(substr($str, 1)) . $str[0];
}

The implementation below takes advantage of the divide and conquer algorithm paradigm, flipping the left and right substrings upon each recursive invocation. Similar to the previous example, if the base-case of a single character string is met, the remaining string is simply returned.

function reverse($str)
{
    if (strlen($str) < 2) {
        return $str;
    }

    $mid = (int) strlen($str) / 2;
    $lft = substr($str, 0, $mid);
    $rgt = substr($str, $mid);

    return reverse($rgt) . reverse($lft);
}

Unicode-Support Implementations

PHP's in-built support for Unicode strings is 'somewhat' lacking, as such, extra steps are required to correctly reverse a string of this type. As Unicode character representations can consist of multiple bytes (i.e. UTF-8), we are unable to naively use 'strlen' and 'str_split' (which assume a character is always a single byte). The implementation below uses 'preg_split' support for Unicode characters, to correctly split the string into characters for us to reverse.

function reverse($str)
{
    return join('', array_reverse(preg_split('//u', $str, -1, PREG_SPLIT_NO_EMPTY)));
}

Using some endianness conversion trickery we are able to use PHP's in-built 'strrev' function. The implementation below uses 'iconv' to achieve the desired results.

function reverse($str)
{
    return iconv('UTF-16LE', 'UTF-8', strrev(iconv('UTF-8', 'UTF-16BE', $str)));
}

Similar in-nature to the previous example, we are instead now using the 'mb' library to perform the conversion.

function reverse($str)
{
    return mb_convert_encoding(strrev(mb_convert_encoding($str, 'UTF-16BE', 'UTF-8')), 'UTF-8', 'UTF-16LE');
}