Инвертировать регистр всех букв в строке (прописные в строчные и строчные в прописные)

Как я могу поменять местами/переключить регистр символов в строке, например:

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

После запуска кода я получаю такой результат:

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

Это вообще возможно?


person tarnfeld    schedule 14.02.2010    source источник


Ответы (9)


Вам нужно будет перебрать строку, проверяя регистр каждого символа, вызывая strtolower() или strtoupper() в зависимости от ситуации, добавляя измененный символ в новую строку.

person Ignacio Vazquez-Abrams    schedule 14.02.2010
comment
Любая идея, как проверить регистр строки? - person tarnfeld; 14.02.2010
comment
Вероятно, это будет работать только для символов ASCII. Альтернативой strotolower() может быть mb_strtolower(). - person Messa; 14.02.2010

Если ваша строка только ASCII, вы можете использовать XOR:

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

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

Выходы:

hELLO, mY nAME IS tOM
person Mike    schedule 07.07.2011
comment
Очень круто. strtolower($str) ^ strtoupper($str) вернет строку с 0x20, где символы альфа и 0 для любого другого символа. Затем xor с исходной строкой использует 0x20, чтобы перевернуть регистр, в то время как символы 0 оставляют неальфа-символы без изменений. - person xtempore; 22.07.2017
comment
@Mike похоже, что это работает с большинством символов UTF-8, когда я использую многобайтовые функции, можете ли вы подтвердить? - person Dawid Zbiński; 28.02.2019

ОК, я знаю, что у вас уже есть ответ, но несколько непонятная функция strtr() очень хочет быть использованной для этого;)

$str = "Hello, My Name is Tom";
echo strtr($str, 
           'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz',
           'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ');
person user272563    schedule 14.02.2010
comment
Если вы хотите работать с многобайтовыми символами UTF-8, вам нужно использовать strtr($str, $substitutions_array). На самом деле это средство, которое я использую для удаления акцентов со всех букв строки UTF8. - person user272563; 14.02.2010
comment
Одним из явных преимуществ этого ответа является то, что это не основанное на регулярных выражениях, не побитовое, однофункциональное решение. Другие методы могут быть менее удобными для команды разработчиков. - person mickmackusa; 07.05.2021

По функциям очень похож на ответ Марка.

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

Объяснение @xtempore:

'a' ^ ' ' возвращает A. Это работает, потому что A — это 0x41, а a — это 0x61 (и аналогично для всех A-Z), а также потому, что пробел — это 0x20. С помощью xor-ing вы переворачиваете этот бит. Проще говоря, вы добавляете 32 к прописным буквам, делая их строчными, и вычитаете 32 из строчных букв, делая их прописными.

person Leigh    schedule 24.09.2012
comment
Как это работает? 'a' ^ ' ' кажется, возвращает 0 для меня. - person Sukima; 29.06.2014
comment
'a' ^ '' возвращает 'A'. Это работает, потому что «A» — это 0x41, а «a» — это 0x61 (и аналогично для всех A-Z), и потому что « » — это 0x20. С помощью xor-ing вы переворачиваете этот бит. Проще говоря, вы добавляете 32 к прописным буквам, делая их строчными, и вычитаете 32 из строчных букв, делая их прописными. - person xtempore; 22.07.2017

Самый быстрый способ - с битовой маской. Никаких неуклюжих строковых функций или регулярных выражений. PHP — это оболочка для C, поэтому мы можем довольно легко манипулировать битами, если вы знаете свою логическую функцию, такую ​​​​как ИЛИ, НЕ, И, 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);

Мы берем N-й символ в $string и выполняем 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_replace или preg_replace, мы просто меняем местами биты, чтобы добавить или вычесть 32 из значения ASCII символа, и мы меняем регистры. 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
person mwieczorek    schedule 16.04.2017

Я знаю, что этот вопрос старый, но вот мои 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);
}
person Larpon    schedule 03.09.2013
comment
preg_split() имеет доступный флаг PREG_SPLIT_NO_EMPTY. Пустой клей — это значение по умолчанию для implode(), и его не нужно объявлять. - person mickmackusa; 29.05.2021

Следующий скрипт поддерживает символы 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śćŻŹ

person KsaR    schedule 25.02.2016
comment
Если смысл метода регулярных выражений состоит в том, чтобы выполнять замены на основе функций в строке, то preg_match_all() менее подходит/прямо по сравнению с preg_replace_callback(). - person mickmackusa; 07.05.2021
comment
@micmackusa, Верно, исправлено. Спасибо. - person KsaR; 08.05.2021

Я полагаю, что решением может быть использование чего-то вроде этого:

$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_strtolower и < a href="http://www.php.net/manual/en/function.mb-strtoupper.php" rel="nofollow noreferrer">mb_strtoupper : это может помочь со специальными символами и многобайтовыми кодировки...

person Pascal MARTIN    schedule 14.02.2010

Для многобайтового/безопасного для юникода решения я бы, вероятно, рекомендовал изменить/переключить регистр каждой буквы в зависимости от того, какая группа захвата содержит букву. Таким образом, вам не нужно выполнять проверку многобайтовой базы после сопоставления буквы с регулярным выражением.

Код: (Демо)

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

См. этот ответ о том, как сопоставлять буквы, которые могут быть многобайтовыми.

person mickmackusa    schedule 20.05.2021