【问题标题】:Invert case of all letters in a string (uppercase to lowercase and lowercase to uppercase)反转字符串中所有字母的大小写(大写转小写,小写转大写)
【发布时间】:2011-01-16 14:38:24
【问题描述】:

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

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

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

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

这可能吗?

【问题讨论】:

    标签: php string case


    【解决方案1】:

    如果你的字符串只是 ASCII,你可以使用 XOR:

    $str = "Hello, My Name is Tom";
    
    print strtolower($str) ^ strtoupper($str) ^ $str;
    

    输出:

    hELLO, mY nAME IS tOM
    

    【讨论】:

    • 非常酷。 strtolower($str) ^ strtoupper($str) 将返回一个 0x20 的字符串,其中字符为 alpha,0 表示任何其他字符。然后与原始字符串 xor 使用 0x20 翻转大小写,而 0 字符保持非字母字符不变。
    • @Mike 似乎在我使用多字节函数时适用于大多数 UTF-8 字符,你能确认一下吗?
    【解决方案2】:

    好的,我知道你已经得到了答案,但是有些晦涩的 strtr() 函数急需用于此;)

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

    【讨论】:

    • 如果要处理多字节 UTF-8 字符,则需要使用 strtr($str, $substitutions_array)。这实际上是我用来从 UTF8 字符串的所有字母中去除重音符号的方法。
    • 这个答案的一个明显优势是它是一种非正则表达式、非按位、单功能解决方案。其他技术可能对“开发团队”不太友好。
    【解决方案3】:

    功能与 Mark 的答案非常相似。

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

    @xtempore 的解释:

    'a' ^ ' ' 返回A。它之所以有效,是因为A 是 0x41,a 是 0x61(同样适用于所有 A-Z),并且因为空格是 0x20。通过异或,您正在翻转那一点。简单来说,就是在大写字母上加 32,使它们变为小写,从小写字母中减去 32,使它们变为大写。

    【讨论】:

    • 这个是如何工作的? 'a' ^ ' ' 似乎为我返回 0
    • 'a' ^ ' ' 返回 'A'。它之所以有效,是因为“A”是 0x41,“a”是 0x61(同样适用于所有 A-Z),并且因为“”是 0x20。通过异或,您正在翻转那一点。简单来说,就是在大写字母上加上 32,使它们变为小写,从小写字母中减去 32,使它们变为大写。
    【解决方案4】:

    最快的方法是使用位掩码。没有笨重的字符串函数或正则表达式。 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 个字符并执行 XOR (^),告诉解释器取 $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 位(右起第 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
    

    【讨论】:

      【解决方案5】:

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

      【讨论】:

      • 知道如何检查字符串的大小写吗?
      • 这可能仅适用于 ASCII 字符。 strotolower() 的替代品可能是 mb_strtolower()
      【解决方案6】:

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

      多功能版: (mb_str_split function found here):

      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()的默认值,不需要声明。
      【解决方案7】:

      以下脚本支持 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śćŻŹ

      【讨论】:

      • 如果正则表达式技术的目的是对字符串进行基于函数的替换,那么preg_match_all()preg_replace_callback() 相比不太合适/直接。
      • @mickmackusa,对,已更正。谢谢。
      【解决方案8】:

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

      $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 :它可能有助于特殊字符和多字节编码...

      【讨论】:

        【解决方案9】:

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

        代码:(Demo)

        $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śćŻŹ
        

        请参阅this answer,了解如何匹配可能是多字节的字母。

        【讨论】:

          猜你喜欢
          • 2016-01-25
          • 1970-01-01
          • 2022-11-11
          • 2016-05-03
          • 1970-01-01
          • 2013-02-04
          • 2015-07-28
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多