PHP - iteruj po znakach łańcuchowych

121

Czy istnieje dobry sposób na iterację znaków w ciągu? Chciałbym być w stanie to zrobić foreach, array_map, array_walk, array_filteritd. W postaci napisu.

Typ casting / juggling nigdzie mnie nie zaprowadził (umieść cały łańcuch jako jeden element tablicy), a najlepszym rozwiązaniem, jakie znalazłem, jest użycie pętli for do skonstruowania tablicy. Wydaje się, że powinno być coś lepszego. Chodzi mi o to, że jeśli możesz na nim indeksować, czy nie powinieneś również być w stanie iterować?

To najlepsze, jakie mam

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

Czy istnieje:

A) Sposób na iterowalność ciągu
B) Lepszy sposób na zbudowanie tablicy znaków z łańcucha (a jeśli tak, to co powiesz na drugą stronę?)

Czuję, że brakuje mi czegoś oczywistego.

jon_darkstar
źródło
Może powinieneś powiedzieć więcej o tym, co próbujesz osiągnąć ... wydaje się, że może być lepszy sposób na zrobienie tego przy użyciu normalnych operacji na łańcuchach.
Vinay Pai,
1
nie mam tutaj prawdziwego celu. tylko ciekawostka, z którą się bawiłem. wydawało się dziwne, że nawet jeśli możesz indeksować ciągi znaków, nie możesz iterować. Nie mogłem nawet wymyślić znaczących przykładowych zastosowań, ale nadal chciałbym wiedzieć, czy jest jakiś sposób na iterację znaków w łańcuchach bez
jawnego tworzenia
to jednak dobra uwaga, oczywiście moje przykłady są dość płytkie. tj. - głównie wszystko, z czym byś zrobił array_filterw tym sensie, można lepiej zrobić za pomocą funkcji string lub reg-ex
jon_darkstar
Rozwiązanie projecteuler.net/problem=20 może być przykładem (choć nieco wymyślonym) przypadku użycia.
Nick Edwards,
jedna uwaga dotycząca for ($ i = 0; $ i <strlen ($ s); $ i ++) Przechowałbym strlen ($ s) w zmiennej przed zapętleniem, w ten sposób nie wywołasz strlen () więcej niż 1 raz
Amin

Odpowiedzi:

176

Krok 1: przekonwertuj ciąg na tablicę za pomocą str_splitfunkcji

$array = str_split($your_string);

Krok 2: pętla przez nowo utworzoną tablicę

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

Możesz sprawdzić dokumentację PHP, aby uzyskać więcej informacji: str_split

SeaBrightSystems
źródło
hah wow. tak to wszystko. i oczywiście imploda może zrobić w innym kierunku. Zaakceptuję to wkrótce, chyba że ktoś pokaże sposób na wykonanie iteracji prosto na żądło
jon_darkstar.
@jon_darkstar Nie znam twojej aplikacji, ale zwróć uwagę, że każdy wpis w tablicy ma znaczący narzut (4 bajty IIRC). Pomiń to, jest znacznie więcej: 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.- Więc str_splitnie może działać z Unicode
Szczęśliwego
86

Iteruj ciąg:

for ($i = 0; $i < strlen($str); $i++){
    echo $str[$i];
}
piekarnik
źródło
7
Wydaje się, że jest to lepsza odpowiedź, ponieważ odpowiada na pytanie - tj. Jak iterować po ciągu w przeciwieństwie do „konwersji na tablicę”.
Robin Andrews,
2
LOL!!!!! Wszystko @OmarTariq. Jest to znacznie wydajniejsze niż udzielona odpowiedź.
0x476f72616e
5
Pamiętaj tylko, że dzwonisz strlen()do każdej iteracji. Nie jest to straszna rzecz, ponieważ PHP ma wstępnie obliczoną długość, ale nadal jest wywołaniem funkcji. Jeśli potrzebujesz szybkości, lepiej zapisz ją w zmiennej przed uruchomieniem pętli.
Vilx
2
To nie jest dobre dla ciągów wielobajtowych, ponieważ tutaj otrzymujemy offset bajtowy, a nie symbol
zawsze
2
@OmarTariq "To jest odpowiedź. Co jest nie tak ze światem?" .... Zła w świecie polega na tym, że świat ma inne języki niż angielski, ta funkcja, jak już powiedziano, będzie iterować bajty w ciągu, a nie znaki.
Księgowy م
20

Jeśli twoje łańcuchy są w Unicode, powinieneś użyć preg_splitz /umodyfikatorem

Z komentarzy w dokumentacji 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
źródło
1
W przypadku ciągów wielobajtowych mb_splitjest bardziej niezawodny.
Élektra
12

Możesz także uzyskać dostęp do $ s1 jak do tablicy, jeśli potrzebujesz tylko dostępu do niej:

$s1 = "hello world";
echo $s1[0]; // -> h
Moritur
źródło
6

Po rozwinięciu z odpowiedzi @SeaBrightSystems możesz spróbować tego:

$s1 = "textasstringwoohoo";
$arr = str_split($s1); //$arr now has character array
Okno mleczarskie
źródło
Nie zgadzam się, ta odpowiedź dodaje wartości, daje działający przykład tego, jak str_split może działać w aplikacji PHP. @SeaBrightSystems to tylko linki do dokumentacji, co czasami nie jest tak pomocne, gdy ktoś próbuje zobaczyć, jak może działać funkcja, na przykładzie. W przeciwnym razie większość odpowiedzi SO to po prostu linki do
php.net
6

Dla tych, którzy szukają najszybszego sposobu na iterację po napisach w php, przygotowałem test porównawczy.
Pierwsza metoda, w której uzyskuje się dostęp do znaków ciągu bezpośrednio, określając jego pozycję w nawiasach i traktując ciąg jako tablicę:

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

Sam uważałem, że ta druga metoda jest najszybszą metodą, ale się myliłem.
Podobnie jak w przypadku drugiej metody (która jest używana w przyjętej odpowiedzi):

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

Ta metoda będzie szybsza, ponieważ używamy prawdziwej tablicy i nie zakładamy, że jest ona tablicą.

Wywołanie ostatniego wiersza każdej z powyższych metod dla 1000000czasów prowadzi do następujących wyników testów porównawczych:

Korzystanie ze stringów [i]
0.24960017204285 Seconds

Korzystanie z str_split
0.18720006942749 Seconds

Co oznacza, że ​​druga metoda jest znacznie szybsza.

AmirHossein
źródło
3

Hmm ... Nie ma potrzeby komplikować sprawy. Podstawy zawsze działają świetnie.

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

Kierunek do przodu:

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

Wyjścia: abcdef

Odwrotny kierunek:

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

Wyjścia: fedcba

Popiół
źródło
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;
}
masakielastic
źródło
Dziwię się, że ta odpowiedź otrzymała tylko 1 głos za: (to najbardziej / jedyna wiarygodna odpowiedź tutaj
Księgowy م
1

Większość odpowiedzi zapomniała o nieangielskich znakach !!!

strlenliczy bajty, a nie znaki, dlatego tak jest i jego funkcje siostrzane działają dobrze ze znakami angielskimi, ponieważ angielskie znaki są przechowywane w 1 bajcie zarówno w kodowaniu UTF-8, jak i ASCII, musisz użyć funkcji wielobajtowych ciągów znaków mb_*

To zadziała z każdym znakiem zakodowanym wUTF-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);
}

To wychodzi

string(1) "a"
string(1) "b"
string(1) "c"
string(1) "d"
string(2) "أ"
string(2) "ب"
string(2) "ت"
string(2) "ث"
Księgowy م
źródło