PHP - iterar en caracteres de cadena

120

¿Existe una buena forma de iterar sobre los caracteres de una cadena? Me gustaría ser capaz de hacer foreach, array_map, array_walk, array_filteretc, sobre los caracteres de una cadena.

La conversión de tipos / malabares no me llevó a ninguna parte (coloque la cadena completa como un elemento de la matriz), y la mejor solución que he encontrado es simplemente usar un bucle for para construir la matriz. Parece que debería haber algo mejor. Quiero decir, si puedes indexarlo, ¿no deberías poder iterar también?

Esto es lo mejor que tengo

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);

¿Hay alguno:

A) Una forma de hacer que la cadena sea iterable
B) Una mejor manera de construir la matriz de caracteres a partir de la cadena (y si es así, ¿qué tal la otra dirección?)

Siento que me estoy perdiendo algo obvio aquí.

jon_darkstar
fuente
Tal vez debería decir más sobre lo que está tratando de lograr ... parece que podría haber una mejor manera de hacerlo utilizando operaciones de cadena normales.
Vinay Pai
1
no tengo un objetivo real aquí. solo una curiosidad con la que estaba jugando. Parecía extraño que, aunque puede indexar cadenas, no pueda iterar. No
podía
Sin embargo, ese es un buen punto, obviamente mis ejemplos son bastante superficiales. es decir, casi todo lo que haría array_filteren este sentido podría hacerse mejor con funciones de cadena o reg-ex
jon_darkstar
Resolver projecteuler.net/problem=20 podría ser un ejemplo (aunque algo artificial) de caso de uso.
Nick Edwards
una nota, con respecto a for ($ i = 0; $ i <strlen ($ s); $ i ++) Almacenaría el strlen ($ s) en una variable antes del bucle, de esta manera no llamarás strlen () más de 1 vez
Amin

Respuestas:

176

Paso 1: convierta la cadena en una matriz usando la str_splitfunción

$array = str_split($your_string);

Paso 2: recorrer la matriz recién creada

foreach ($array as $char) {
 echo $char;
}

Puede consultar los documentos de PHP para obtener más información: str_split

SeaBrightSystems
fuente
hah wow. sí, eso es todo. y por supuesto implosionar puede hacer la otra dirección. Aceptaré esto pronto, a menos que alguien pueda mostrar una manera de hacer la iteración directamente en el momento
oportuno
@jon_darkstar No conozco su aplicación, pero tenga en cuenta que cada entrada en una matriz tiene una sobrecarga significativa (4bytes IIRC). Omita eso, es 'bastante' mucho más: nikic.github.com/2011/12/12/…
Daan Timmer
str_split() will split into bytes, rather than characters when dealing with a multi-byte encoded string.- Entonces str_splitno puedo trabajar con Unicode
Feliz
85

Iterar cadena:

for ($i = 0; $i < strlen($str); $i++){
    echo $str[$i];
}
Owen
fuente
7
Esta parece una mejor respuesta porque responde a la pregunta, es decir, cómo iterar sobre una cadena en lugar de 'convertir en matriz'.
Robin Andrews
2
LOL !!!!! Todo @OmarTariq. Esto es mucho más eficiente que la respuesta proporcionada.
0x476f72616e
5
Solo tenga en cuenta que está llamando strlen()a cada iteración. No es algo terrible, ya que PHP tiene la longitud precalculada, pero sigue siendo una llamada de función. Si necesita velocidad, mejor guárdelo en una variable antes de iniciar el ciclo.
Vilx-
2
Esto no es bueno para cadenas multibyte, porque aquí estamos obteniendo un desplazamiento de bytes, no un símbolo
alvery
2
@OmarTariq "Esta es la respuesta. ¿Qué le pasa al mundo?" .... El problema con el mundo es que el mundo tiene otros idiomas además del inglés, esta función, como dijo Alvery, iterará los bytes en la cadena, no los caracteres.
Contador
20

Si sus cadenas están en Unicode, debe usar preg_splitcon /umodificador

De los comentarios en la documentación de php:

function mb_str_split( $string ) { 
    # Split at all position not after the start: ^ 
    # and not before the end: $ 
    return preg_split('/(?<!^)(?!$)/u', $string ); 
} 
Dawid Ohia
fuente
1
Para cadenas multibyte, mb_splites más confiable.
Élektra
12

También puede acceder a $ s1 como una matriz, si solo necesita acceder a él:

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

Ampliado de la respuesta de @SeaBrightSystems, puedes probar esto:

$s1 = "textasstringwoohoo";
$arr = str_split($s1); //$arr now has character array
Ventana de productos lácteos
fuente
No estoy de acuerdo, esta respuesta agrega valor, brinda un ejemplo práctico de cómo str_split podría funcionar en una aplicación PHP. @SeaBrightSystems solo enlaza con la documentación, lo que a veces no es tan útil cuando una persona está tratando de ver cómo puede funcionar una función, dado un ejemplo. De lo contrario, la mayoría de las respuestas SO serían solo enlaces a php.net
kurdtpage
6

Para aquellos que buscan la forma más rápida de iterar sobre cadenas en php, he preparado una prueba de referencia.
El primer método en el que accede a caracteres de cadena directamente especificando su posición entre paréntesis y tratando la cadena como una matriz:

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

Yo mismo pensé que este último es el método más rápido, pero estaba equivocado.
Al igual que con el segundo método (que se usa en la respuesta aceptada):

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

Este método será más rápido porque estamos usando una matriz real y no asumimos que una sea una matriz.

Llamar a la última línea de cada uno de los métodos anteriores para los 1000000tiempos conduce a estos resultados de evaluación comparativa:

Usando cadena [i]
0.24960017204285 Seconds

Usando str_split
0.18720006942749 Seconds

Lo que significa que el segundo método es mucho más rápido.

AmirHossein
fuente
3

Hmm ... No hay necesidad de complicar las cosas. Lo básico siempre funciona muy bien.

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

Dirección de avance:

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

Salidas: abcdef

Direccion contraria:

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

Salidas: fedcba

Ceniza
fuente
2
// 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;
}
masakielástica
fuente
Me sorprende que esta respuesta solo obtuviera 1 voto a favor :( esta es la respuesta más / única confiable aquí
Contador م
1

¡La mayoría de las respuestas se olvidaron de los caracteres no ingleses!

strlencuenta BYTES, no caracteres, por eso es así y sus funciones hermanas funcionan bien con caracteres en inglés, porque los caracteres en inglés se almacenan en 1 byte en codificaciones UTF-8 y ASCII, necesita usar las funciones de cadena multibyte mb_*

Esto funcionará con cualquier carácter codificado enUTF-8

// 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);
}

Esto salidas

string(1) "a"
string(1) "b"
string(1) "c"
string(1) "d"
string(2) "أ"
string(2) "ب"
string(2) "ت"
string(2) "ث"
Contador م
fuente