如何反转一个Unicode字符串

14
在对这个问题的回答的评论中,暗示了PHP无法反转Unicode字符串。
对于Unicode,PHP可以工作,因为大多数应用程序将其处理为二进制。是的,PHP是8位清洁的。在PHP中尝试相当于这个的等效物:perl -Mutf8 -e 'print scalar reverse("ほげほげ")' 你会得到垃圾,而不是"げほげほ"。- jrockway
不幸的是,PHP的Unicode支持目前最多只能说是"不足"。这将随着PHP6的到来而有着巨大的改变。
PHP的MultiByte函数提供了处理Unicode所需的基本功能,但它是不一致的,并且缺少很多函数。其中一个就是反转字符串的函数。
当然,我只是想反转这段文本,没有其他原因,只是为了弄清楚是否可能。我编写了一个函数来完成这个巨大而复杂的任务,以反转这段Unicode文本,所以你可以再放松一会儿,直到PHP6发布。
测试代码:
$enc = 'UTF-8';
$text = "ほげほげ";
$defaultEnc = mb_internal_encoding();

echo "Showing results with encoding $defaultEnc.\n\n";

$revNormal = strrev($text);
$revInt = mb_strrev($text);
$revEnc = mb_strrev($text, $enc);

echo "Original text is: $text .\n";
echo "Normal strrev output: " . $revNormal . ".\n";
echo "mb_strrev without encoding output: $revInt.\n";
echo "mb_strrev with encoding $enc output: $revEnc.\n";

if (mb_internal_encoding($enc)) {
    echo "\nSetting internal encoding to $enc from $defaultEnc.\n\n";
    
    $revNormal = strrev($text);
    $revInt = mb_strrev($text);
    $revEnc = mb_strrev($text, $enc);
    
    echo "Original text is: $text .\n";
    echo "Normal strrev output: " . $revNormal . ".\n";
    echo "mb_strrev without encoding output: $revInt.\n";
    echo "mb_strrev with encoding $enc output: $revEnc.\n";
    
} else {
    echo "\nCould not set internal encoding to $enc!\n";
}
6个回答

9

这里有另一种使用正则表达式的方法:

function utf8_strrev($str){
 preg_match_all('/./us', $str, $ar);
 return implode(array_reverse($ar[0]));
}

7

这里有另一种方法。似乎不需要指定输出编码就可以正常工作(已经测试过几种不同的mb_internal_encoding):

function mb_strrev($text)
{
    return join('', array_reverse(
        preg_split('~~u', $text, -1, PREG_SPLIT_NO_EMPTY)
    ));
}

+1。这让我想起了在Perl中实现它的方式: my $reversed = join '', reverse split /(\X)/, $original; - LozzerJP

7

图形函数比mbstring和PCRE函数更正确地处理UTF-8字符串/ Mbstring和PCRE可能会破坏字符。您可以通过执行以下代码来查看它们之间的区别。

function str_to_array($string)
{
    $length = grapheme_strlen($string);
    $ret = [];

    for ($i = 0; $i < $length; $i += 1) {

        $ret[] = grapheme_substr($string, $i, 1);
    }

    return $ret;
}

function str_to_array2($string)
{
    $length = mb_strlen($string, "UTF-8");
    $ret = [];

    for ($i = 0; $i < $length; $i += 1) {

    $ret[] = mb_substr($string, $i, 1, "UTF-8");
}

    return $ret;
}

function str_to_array3($string)
{
    return preg_split('//u', $string, -1, PREG_SPLIT_NO_EMPTY);
}

function utf8_strrev($string)
{
    return implode(array_reverse(str_to_array($string)));
}

function utf8_strrev2($string)
{
    return implode(array_reverse(str_to_array2($string)));
}

function utf8_strrev3($string)
{
    return implode(array_reverse(str_to_array3($string)));
}

// http://www.php.net/manual/en/function.grapheme-strlen.php
$string = "a\xCC\x8A"  // 'LATIN SMALL LETTER A WITH RING ABOVE' (U+00E5)
         ."o\xCC\x88"; // 'LATIN SMALL LETTER O WITH DIAERESIS'  (U+00F6)

var_dump(array_map(function($elem) { return strtoupper(bin2hex($elem)); },
[
  'should be' => "o\xCC\x88"."a\xCC\x8A",
  'grapheme' => utf8_strrev($string),
  'mbstring' => utf8_strrev2($string),
  'pcre' => utf8_strrev3($string)
]));

结果在这里。
array(4) {
  ["should be"]=>
  string(12) "6FCC8861CC8A"
  ["grapheme"]=>
  string(12) "6FCC8861CC8A"
  ["mbstring"]=>
  string(12) "CC886FCC8A61"
  ["pcre"]=>
  string(12) "CC886FCC8A61"
}

自PHP 5.5 (intl 3.0)起,可以使用IntlBreakIterator。

function utf8_strrev($str)
{
    $it = IntlBreakIterator::createCodePointInstance();
    $it->setText($str);

    $ret = '';
    $pos = 0;
    $prev = 0;

    foreach ($it as $pos) {
        $ret = substr($str, $prev, $pos - $prev) . $ret;
        $prev = $pos;
    }

    return $ret;  
}

由于几乎所有的图形和 mbstring 函数都无法处理无效的字节序列,因此需要使用替代字符替换无效的字节序列。如果您对此话题感兴趣,请参阅 https://dev59.com/IV3Va4cB1Zd3GeqPA3ko#13695364 了解详细信息。 - masakielastic

4
答案
function mb_strrev($text, $encoding = null)
{
    $funcParams = array($text);
    if ($encoding !== null)
        $funcParams[] = $encoding;
    $length = call_user_func_array('mb_strlen', $funcParams);

    $output = '';
    $funcParams = array($text, $length, 1);
    if ($encoding !== null)
        $funcParams[] = $encoding;
    while ($funcParams[1]--) {
         $output .= call_user_func_array('mb_substr', $funcParams);
    }
    return $output;
}

1

另一种方法:

function mb_strrev($str, $enc = null) {
    if(is_null($enc)) $enc = mb_internal_encoding();
    $str = mb_convert_encoding($str, 'UTF-16BE', $enc);
    return mb_convert_encoding(strrev($str), $enc, 'UTF-16LE');
}

0

这很容易 utf8_strrev( $str )。看一下我复制在下面的我的库的相关源代码

function utf8_strrev( $str )
{
    return implode( array_reverse( utf8_split( $str ) ) );
}

function utf8_split( $str , $split_length = 1 )
{
    $str    = ( string ) $str;

    $ret    = array( );

    if( pcre_utf8_support( ) )
    {
        $str    = utf8_clean( $str );

        $ret    = preg_split('/(?<!^)(?!$)/u', $str );

        // \X is buggy in many recent versions of PHP
        //preg_match_all( '/\X/u' , $str , $ret );
        //$ret  = $ret[0];
    }
    else
    {
        //Fallback

        $len    = strlen( $str );

        for( $i = 0 ; $i < $len ; $i++ )
        {
            if( ( $str[$i] & "\x80" ) === "\x00" )
            {
                $ret[]  = $str[$i];
            }
            else if( ( ( $str[$i] & "\xE0" ) === "\xC0" ) && ( isset( $str[$i+1] ) ) )
            {
                if( ( $str[$i+1] & "\xC0" ) === "\x80" )
                {
                    $ret[]  = $str[$i] . $str[$i+1];

                    $i++;
                }
            }
            else if( ( ( $str[$i] & "\xF0" ) === "\xE0" ) && ( isset( $str[$i+2] ) ) )
            {
                if( ( ( $str[$i+1] & "\xC0" ) === "\x80" ) && ( ( $str[$i+2] & "\xC0" ) === "\x80" ) )
                {
                    $ret[]  = $str[$i] . $str[$i+1] . $str[$i+2];

                    $i  = $i + 2;
                }
            }
            else if( ( ( $str[$i] & "\xF8" ) === "\xF0" ) && ( isset( $str[$i+3] ) ) )
            {
                if( ( ( $str[$i+1] & "\xC0" ) === "\x80" ) && ( ( $str[$i+2] & "\xC0" ) === "\x80" ) && ( ( $str[$i+3] & "\xC0" ) === "\x80" ) )
                {
                    $ret[]  = $str[$i] . $str[$i+1] . $str[$i+2] . $str[$i+3];

                    $i  = $i + 3;
                }
            }
        }
    }


    if( $split_length > 1 )
    {
        $ret = array_chunk( $ret , $split_length );

        $ret    = array_map( 'implode' , $ret );
    }

    if( $ret[0] === '' )
    {
        return array( );
    }

    return $ret;
}


function utf8_clean( $str , $remove_bom = false )
{
    $regx = '/([\x00-\x7F]|[\xC0-\xDF][\x80-\xBF]|[\xE0-\xEF][\x80-\xBF]{2}|[\xF0-\xF7][\x80-\xBF]{3})|./s';

    $str    = preg_replace( $regx , '$1' , $str );

    if( $remove_bom )
    {
        $str    = utf8_str_replace( utf8_bom( ) , '' , $str );
    }

    return $str;
}


function utf8_str_replace( $search , $replace , $subject , &$count = 0 )
{
    return str_replace( $search , $replace , $subject , $count );
}


function utf8_bom( )
{
    return "\xef\xbb\xbf";

}


function pcre_utf8_support( )
{
    static $support;

    if( !isset( $support ) )
    {
        $support = @preg_match( '//u', '' );
        //Cached the response
    }

    return $support;
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接