在一个字符串中反转所有字母的大小写(大写变小写,小写变大写)

14

如何交换/切换字符串中字符的大小写?例如:

$str = "Hello, My Name is Tom";

运行代码后,我得到了以下结果:

$newstr = "hELLO, mY nAME Is tOM";

这真的可能吗?

9个回答

69

如果你的字符串只包含ASCII字符,你可以使用异或运算:

$str = "Hello, My Name is Tom";

print strtolower($str) ^ strtoupper($str) ^ $str;

输出:

hELLO, mY nAME IS tOM

1
非常酷。strtolower($str) ^ strtoupper($str) 将返回一个字符串,其中字符为字母的位置为0x20,其他字符为0。然后与原始字符串进行异或运算,使用0x20来翻转大小写,而0字符则保持非字母字符不变。 - xtempore
@Mike 看起来当我使用多字节函数时,这适用于大多数UTF-8字符,你能确认一下吗? - Dawid Zbiński

12

好的,我知道你已经得到了一个答案,但是有点模糊的 strtr() 函数非常适合用于这个问题 ;)

$str = "Hello, My Name is Tom";
echo strtr($str, 
           'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz',
           'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ');

如果你想处理多字节的UTF-8字符,你需要使用strtr($str, $substitutions_array)。这实际上是我用来从UTF8字符串中去除重音的方法。 - user272563
这个答案的一个明显优点是它是非正则表达式、非位运算、单函数解决方案。其他技术可能不太适合“开发团队”。 - mickmackusa

6

和 Mark 的回答在功能上非常相似。

preg_replace_callback(
    '/[a-z]/i',
    function($matches) {
        return $matches[0] ^ ' ';
    },
    $str
)

@xtempore的解释:

'a' ^ ' ' 返回 A。这个操作有效是因为 A 是 0x41,a 是 0x61(其他所有的大写字母也是如此),空格是 0x20。通过异或操作,你可以翻转一个比特位。简单来说,你可以给大写字母加上32使它们变成小写字母,给小写字母减去32使它们变成大写字母。


这个是怎么工作的?对我来说,'a' ^ ' ' 似乎返回 0 - Sukima
'a' ^ ' ' 返回 'A'。这是因为 'A' 是 0x41,'a' 是 0x61(所有 A-Z 同理),而 ' ' 是 0x20。通过异或运算,您可以翻转那个位。简单来说,您正在将 32 添加到大写字母,使它们变成小写字母,并从小写字母中减去 32,使它们变成大写字母。 - xtempore

6
最快的方法是使用位掩码。不需要笨重的字符串函数或正则表达式。PHP是C语言的包装器,因此我们可以很容易地操作位,只要您知道逻辑函数(如OR、NOT、AND、XOR、NAND等)即可:
function swapCase($string) {
    for ($i = 0; $i < strlen($string); $i++) {
        $char = ord($string{$i});
        if (($char > 64 && $char < 91) || ($char > 96 && $char < 123)) {
            $string{$i} = chr($char ^ 32);
        }
    }
    return $string;
}

这是导致变化的原因:
$string{$i} = chr($char ^ 32);

我们从变量 $string 中取出第 N 个字符,并对其执行异或(^)操作,告诉解释器获取整数值 $char 并交换第6位(32)从1到0或0到1的值。
所有 ASCII 字符与其对应字符相差 32(ASCII 是一个巧妙的设计,因为这点很好利用,由于32是2的幂(2^5),所以可以轻松地移位。要获取字母的 ASCII 值,请使用内置的 PHP 函数 ord()
ord('a') // 65
ord('A') // 97
// 97 - 65 = 32

所以你使用 strlen() 作为 for 循环的中间部分循环遍历字符串,它将正好遍历字符串中字母的数量次数。如果在位置 $i 的字符是一个字母 (a-z (65-90) 或 A-Z (97-122)),它将使用位掩码将该字符交换为其大写或小写形式。

以下是位掩码的工作原理:

0100 0001 // 65 (lowercase a)
0010 0000 // 32 (bitmask of 32)
--------- // XOR means: we put a 1 if the bits are different, a 0 if they are same.
0110 0001 // 97 (uppercase A)

我们可以将它反转:
0110 0001 // 97 (A)
0010 0000 // Bitmask of 32
---------
0100 0001 // 65 (a)

无需使用str_replacepreg_replace,我们只需要交换位来从字符的ASCII值中加上或减去32,并交换大小写。第6位(从右边数)确定字符是大写还是小写。如果它是0,则为小写,如果是1则为大写。将位从0更改为1添加32,获取大写的chr()值,将1更改为0则减去32,将大写字母变成小写。
swapCase('userId'); // USERiD
swapCase('USERiD'); // userId
swapCase('rot13'); // ROT13

我们还可以编写一个函数来交换特定字符的大小写:

我们可以编写一个函数来交换特定字符的大小写:

// $i = position in string
function swapCaseAtChar($string, $i) {
    $char = ord($string{$i});
    if (($char > 64 && $char < 91) || ($char > 96 && $char < 123)) {
        $string{$i} = chr($char ^ 32);
        return $string;
    } else {
        return $string;
    }
}

echo swapCaseAtChar('iiiiiiii', 0); // Iiiiiiii
echo swapCaseAtChar('userid', 4); // userId

// Numbers are no issue
echo swapCaseAtChar('12345qqq', 7); // 12345qqQ

3

您需要遍历字符串并测试每个字符的大小写,根据需要调用strtolower()strtoupper(),将修改后的字符添加到新字符串中。


有没有想法如何检查字符串的大小写? - tarnfeld
2
这可能仅适用于ASCII字符。strtolower()的替代方法可能是mb_strtolower() - Messa

2

以下脚本支持像"ą"这样的UTF-8字符。

  • PHP 7.1+

    $before = 'aaAAąAŚĆżź';
    $after = preg_replace_callback('/./u', function (array $char) {
        [$char] = $char;
    
        return $char === ($charLower = mb_strtolower($char))
        ? mb_strtoupper($char)
        : $charLower;
    }, $before);
    
  • PHP 7.4+

    $before = 'aaAAąAŚĆżź';
    $after = implode(array_map(function (string $char) {
        return $char === ($charLower = mb_strtolower($char))
        ? mb_strtoupper($char)
        : $charLower;
    }, mb_str_split($before)));
    

$before: aaAAąAŚĆżź

$after: AAaaĄaśćŻŹ


1
如果正则表达式技术的目的是在字符串上进行基于函数的替换,那么与preg_replace_callback()相比,preg_match_all()就不太合适/直接了。 - mickmackusa
@mickmackusa,没错,已经更正了。谢谢。 - KsaR

2

我知道这个问题很老了 - 但这是我关于多字节实现的两种方式。

多功能版本: (mb_str_split 函数在这里找到):

function mb_str_split( $string ) { 
   # Split at all position not after the start: ^ 
   # and not before the end: $ 
   return preg_split('/(?<!^)(?!$)/u', $string ); 
}

function mb_is_upper($char) {
   return mb_strtolower($char, "UTF-8") != $char;
}

function mb_flip_case($string) {
   $characters = mb_str_split($string);
   foreach($characters as $key => $character) {
       if(mb_is_upper($character))
           $character = mb_strtolower($character, 'UTF-8');
       else
           $character = mb_strtoupper($character, 'UTF-8');

       $characters[$key] = $character;
   }
   return implode('',$characters);
}

单一功能版本:

function mb_flip_case($string) {
    $characters = preg_split('/(?<!^)(?!$)/u', $string );
    foreach($characters as $key => $character) {
        if(mb_strtolower($character, "UTF-8") != $character)
            $character = mb_strtolower($character, 'UTF-8');
        else
            $character = mb_strtoupper($character, 'UTF-8');

        $characters[$key] = $character;
    }
    return implode('',$characters);
}

preg_split()有一个可用的PREG_SPLIT_NO_EMPTY标志。空胶水是implode()的默认值,不需要声明。 - mickmackusa

1

我想解决方案可能是使用类似这样的东西:

$str = "Hello, My Name is Tom";
$newStr = '';
$length = strlen($str);
for ($i=0 ; $i<$length ; $i++) {
    if ($str[$i] >= 'A' && $str[$i] <= 'Z') {
        $newStr .= strtolower($str[$i]);
    } else if ($str[$i] >= 'a' && $str[$i] <= 'z') {
        $newStr .= strtoupper($str[$i]);
    } else {
        $newStr .= $str[$i];
    }
}
echo $newStr;

这将为您提供:

hELLO, mY nAME IS tOM


即:

  • 循环遍历原始字符串中的每个字符
  • 如果它在A和Z之间,则将其转换为小写字母
  • 如果它在a和z之间,则将其转换为大写字母
  • 否则,保留原样

问题在于,这种方法可能无法很好地处理特殊字符,如重音符号 :-(


以下是一个快速提案,对于其他某些字符可能有效(也可能无效):

$str = "Hello, My Name is Tom";
$newStr = '';
$length = strlen($str);
for ($i=0 ; $i<$length ; $i++) {
    if (strtoupper($str[$i]) == $str[$i]) {
        // Putting to upper case doesn't change the character
        // => it's already in upper case => must be put to lower case
        $newStr .= strtolower($str[$i]);
    } else {
        // Putting to upper changes the character
        // => it's in lower case => must be transformed to upper case
        $newStr .= strtoupper($str[$i]);
    }
}
echo $newStr;

一个想法是使用 mb_strtolowermb_strtoupper:这可能有助于处理特殊字符和多字节编码...

0

对于一个多字节/Unicode安全的解决方案,我可能会建议根据包含字母的捕获组来改变/切换每个字母的大小写。这样,您就不必在使用正则表达式匹配字母后进行多字节基础检查。

代码:(演示)

$string = 'aaAAąAŚĆżź';
echo preg_replace_callback(
         '/(\p{Lu})|(\p{Ll})/u',
         function($m) {
             return $m[1]
                 ? mb_strtolower($m[1])
                 : mb_strtoupper($m[2]);
         },
         $string
     );
// AAaaĄaśćŻŹ

请参考这个答案,了解如何匹配可能是多字节的字母。


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