PHP - 迭代字符串中的字符

171
有没有一种很好的方式来遍历字符串中的每个字符?我想能够像对待数组一样使用 foreach, array_map, array_walk, array_filter 等方法来处理字符串的每个字符。
强制类型转换并不能帮助我(它会将整个字符串看作一个数组元素),而我找到的最佳解决方案是使用 for 循环构建数组。但我感觉应该有更好的方法。我的意思是,如果可以对其进行索引,那么也应该能够进行迭代才对吧?
这就是我目前掌握的最佳方法了。
function stringToArray($s)
{
    $r = array();
    for($i=0; $i<strlen($s); $i++) 
         $r[$i] = $s[$i];
    return $r;
}

$s1 = "textasstringwoohoo";
$arr = stringToArray($s1); //$arr now has character array

$ascval = array_map('ord', $arr);  //so i can do stuff like this
$foreach ($arr as $curChar) {....}
$evenAsciiOnly = array_filter( function($x) {return ord($x) % 2 === 0;}, $arr);

有没有以下两种方式之一:
A) 使字符串可迭代的方法
B) 构建字符数组更好的方法(如果有的话,如何实现另一种方向?)

我感觉我错过了什么显而易见的东西。


也许你应该多谈一谈你想要实现什么...使用普通的字符串操作似乎有更好的方法来完成它。 - Vinay Pai
1
这里没有真正的目标。只是我在玩的好奇心。虽然你可以在字符串上进行索引,但似乎无法迭代。我甚至想不出有意义的用例示例,但我仍然想知道是否有一些方法可以在不明确构造字符数组的情况下迭代字符串字符。 - jon_darkstar
这是一个很好的观点,显然我的例子都很浅显。也就是说,在这种情况下,你用 array_filter 做的大多数事情都可以用字符串或正则表达式函数更好地完成。 - jon_darkstar
解决https://projecteuler.net/problem=20可能是一个例子(虽然有些牵强)的用例。 - Nick Edwards
一个注意事项,关于 for($i=0; $i<strlen($s); $i++)。我会在循环之前将 strlen($s) 存储在一个变量中,这样你就不会调用超过 1 次的 strlen() 函数了。 - Amin
字符串清理是使用此功能的一个很好的例子。如果您想要将所有的'%'替换为 '[%]',您只需要使用str_replace。但是,如果您想要将所有的'['替换为'[[]',并将所有的']'替换为'[]]',则需要迭代字符串以测试每个字符,以防止替换相互覆盖。 - danielson317
9个回答

243

使用str_split迭代ASCII字符串(PHP 5.0起可用)

如果您的字符串仅包含ASCII(即“英文”)字符,请使用str_split

$str = 'some text';
foreach (str_split($str) as $char) {
    var_dump($char);
}

使用mb_str_split迭代Unicode字符串(自PHP 7.4起)

如果您的字符串可能包含Unicode(即“非英语”)字符,则必须使用mb_str_split

$str = 'μυρτιὲς δὲν θὰ βρῶ';
foreach (mb_str_split($str) as $char) {
    var_dump($char);
}

@jon_darkstar我不了解你的应用程序,但请注意数组中的每个条目都有显着的开销(4个字节,如果我没记错)。跳过这个,在这里可以找到更详细的信息:http://nikic.github.com/2011/12/12/How-big-are-PHP-arrays-really-Hint-BIG.html - Daan Timmer
2
当处理多字节编码字符串时,str_split()将会按字节进行分割,而不是字符。因此,str_split()无法处理Unicode。 - Happy
2
mb_str_split would be the multi-byte equivalent. $array = mb_str_split($your_string); - LStarky
为什么循环没有简化为foreach (str_split($your_string) as $char) - emkey08
请注意,即使在空字符串的情况下,str_split()函数也会产生至少一个元素,这将导致至少一次迭代。在这种情况下,这可能是棘手错误的一个好源头。 - Demis Palma ツ
@DemisPalmaツ True 对于 PHP 8.2 之前的版本成立。自从 PHP 8.2 版本起,该 bug 已经被修复。请参阅 PHP 8.2 升级说明 - emkey08

126

遍历字符串:

for ($i = 0; $i < strlen($str); $i++){
    echo $str[$i];
}

10
这个答案似乎更好,因为它回答了问题——即如何迭代一个字符串,而不是“转换为数组”。 - Robin Andrews
3
LOL!!!!! 一切 @OmarTariq。这比提供的答案更有效率。 - user5550963
12
请注意,您正在每次迭代中调用 strlen()。虽然 PHP 已经预先计算了长度,但仍是一个函数调用。如果您需要更快的速度,最好在开始循环之前将其保存在变量中。 - Vilx-
5
这对多字节字符串不利,因为我们在此获取的是字节偏移量而非符号。 - alvery
6
“这是答案。世界有什么问题吗?”……世界的问题在于世界上存在英语以外的其他语言。正如Alvery所说,该函数将迭代字符串中的字节,而不是字符。 - Accountant م
显示剩余2条评论

21
如果你的字符串是Unicode编码,那么你应该使用带有/u修饰符的preg_split函数进行分割。
来自PHP文档评论部分的信息:
function mb_str_split( $string ) { 
    # Split at all position not after the start: ^ 
    # and not before the end: $ 
    return preg_split('/(?<!^)(?!$)/u', $string ); 
} 

3
对于多字节字符串,“mb_split”更加可靠。 - Lux
需要引用@Lux。 - mickmackusa
@mickmackusa 已经过去了几年(如果你使用的是 PHP≥7.4,那么现在应该使用stdlib mb_str_split),我真的不记得我当时的意思是什么,但我的猜测是preg_split与 /.../u 仅支持UTF-8(而不是OP所说的“Unicode”),而 mb_split 允许任意编码(此外, mb_split 明确设计用于多字节字符串的正则表达式拆分,因此它可能具有一些额外的优化等?并且通常情况下,由于它是专为此目的而构建的,我的默认假设是它比/u PCRE扩展更可靠和/或完整) - Lux
我个人不知道mb_str_split()preg_split('//u', $string)之间有什么区别。我只是想说,重要的是我们不要基于假设来持续传播可能错误的声明。如果一种技术被证明比另一种技术差,我们应该能够证实这个事实。 - mickmackusa
耶!感谢你让我注意到这个问题。不幸的是,现在已经太晚了,我无法编辑原来的评论,但希望我的后续说明能够澄清我的意思;顺便提供一下 这里这里 的信息,因为之前的评论字数超限了。 - Lux

14

如果你只需要访问$s1,你也可以像访问数组一样直接访问:

$s1 = "hello world";
echo $s1[0]; // -> h

9
大多数答案都忘记了非英语字符!!! strlen 计算的是字节数而不是字符数,这就是为什么它及其兄弟函数在处理英语字符时能够正常工作,因为英语字符在 UTF-8 和 ASCII 编码中均以 1 字节存储。如果要处理任何使用 UTF-8 编码的字符,您需要使用多字节字符串函数 mb_*
// 8 characters in 12 bytes
$string = "abcdأبتث";

$charsCount = mb_strlen($string, 'UTF-8');
for($i = 0; $i < $charsCount; $i++){
    $char = mb_substr($string, $i, 1, 'UTF-8');
    var_dump($char);
}

这个输出的结果与it技术有关。
string(1) "a"
string(1) "b"
string(1) "c"
string(1) "d"
string(2) "أ"
string(2) "ب"
string(2) "ت"
string(2) "ث"

8

对于那些寻求PHP中最快的字符串迭代方法的人,我已经准备了基准测试。
第一种方法是直接通过在括号中指定位置并像数组一样处理字符串来访问字符串字符:

$string = "a sample string for testing";
$char = $string[4] // equals to m

我自己认为后者是最快的方法,但我错了。
就像第二种方法(被采纳的答案中使用的方法)一样:
$string = "a sample string for testing";
$string = str_split($string);
$char = $string[4] // equals to m

这种方法会更快,因为我们使用了一个真正的数组而不是假定某个是数组。

对于上述每种方法的最后一行进行1000000次调用,得到以下基准测试结果:

使用string[i]
0.24960017204285秒

使用str_split
0.18720006942749秒

这意味着第二种方法要快得多。


6

从@SeaBrightSystems的回答扩展开来,您可以尝试这样做:

$s1 = "textasstringwoohoo";
$arr = str_split($s1); //$arr now has character array

我不同意,这个答案确实有价值,它提供了一个在PHP应用程序中如何使用str_split的工作示例。@SeaBrightSystems只是链接到文档,有时当一个人试图看到一个函数如何工作时,这并不是很有帮助的,给出一个示例会更好。否则,大多数SO答案只会链接到php.net。 - kurdtpage

5

嗯……没有必要把事情搞得太复杂。基础知识总是很好用的。

    $string = 'abcdef';
    $len = strlen( $string );
    $x = 0;

正向传递:

while ( $len > $x ) echo $string[ $x++ ];

输出:abcdef

反转方向:

while ( $len ) echo $string[ --$len ];

输出:fedcba


3
// Unicode Codepoint Escape Syntax in PHP 7.0
$str = "cat!\u{1F431}";

// IIFE (Immediately Invoked Function Expression) in PHP 7.0
$gen = (function(string $str) {
    for ($i = 0, $len = mb_strlen($str); $i < $len; ++$i) {
        yield mb_substr($str, $i, 1);
    }
})($str);

var_dump(
    true === $gen instanceof Traversable,
    // PHP 7.1
    true === is_iterable($gen)
);

foreach ($gen as $char) {
    echo $char, PHP_EOL;
}

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