Ітерація над кожним рядком у рядку PHP


130

У мене є форма, яка дозволяє користувачеві завантажувати текстовий файл або копіювати / вставляти вміст файлу в текстову область. Я легко можу розмежовувати обидва і розміщувати ту, яку б вони не ввели в рядкову змінну, але куди мені піти звідти?

Мені потрібно перебирати кожен рядок рядка (бажано, не турбуючись про нові рядки на різних машинах), переконайтесь, що в ньому є рівно один маркер (немає пробілів, вкладок, коми тощо), саніруйте дані, а потім генеруйте запит SQL виходячи з усіх ліній.

Я досить хороший програміст, тому знаю загальну думку про те, як це зробити, але минуло так довго, як я працював з PHP, що відчуваю, що шукаю неправильні речі і, таким чином, приходжую до марної інформації. Ключова проблема, яка у мене виникає, полягає в тому, що я хочу читати вміст рядка рядок. Якби це файл, це було б легко.

Я в основному шукаю корисні функції PHP, а не алгоритм, як це зробити. Будь-які пропозиції?


Ви можете спочатку нормалізувати нові рядки. Цей метод s($myString)->normalizeLineEndings()доступний за допомогою github.com/delight-im/PHP-Str (бібліотека з ліцензією MIT), яка має безліч інших корисних допоміжних рядків. Ви можете поглянути на вихідний код.
каре

Відповіді:


190

preg_split змінна, що містить текст, і повторіть над поверненим масивом:

foreach(preg_split("/((\r?\n)|(\r\n?))/", $subject) as $line){
    // do stuff with $line
} 

Чи буде це обробляти ^ M на додаток до \ n \ r?
Topher Fangio

Я не впевнений, що повернення каретки ascii перетворюється на \, коли він розміщений всередині змінної. Якщо ні, ви завжди можете використовувати split () / exlope () зі значенням ascii натомість - ch (13)
Кирило

12
Краще регулярне виразка /((\r?\n)|(\r\n?))/.
Фелікс Сапареллі

3
Щоб відповідати Unix LF (\ n), MacOS <9 CR (\ r), Windows CR + LF (\ r \ n) та рідкісним LF + CR (\ n \ r), це повинно бути:/((\r?\n)|(\n?\r))/
Очікування Dev ...

2
Це, ймовірно, бомби катастрофічно для багатобайтових даних.
pguardiario

158

Я хотів би запропонувати суттєво більш швидку (а також ефективну пам'ять) альтернативу: strtokа не preg_split.

$separator = "\r\n";
$line = strtok($subject, $separator);

while ($line !== false) {
    # do something with $line
    $line = strtok( $separator );
}

Тестуючи продуктивність, я повторив 100 разів тестовий файл із 17 тис. Рядків: preg_splitзайняв 27,7 секунди, тоді як strtokзайняв 1,4 секунди.

Зауважте, що хоча $separator визначене як "\r\n", strtokбуде відокремлено або від символу - і з PHP4.1.0, пропускайте порожні рядки / жетони.

Дивіться ручний запис strtok: http://php.net/strtok


21
+1 для міркувань продуктивності при роботі з великими наборами ліній.
CodeAngry

4
Хоча ця функція api - це загальний безлад (дзвінок з різними параметрами), це найкраще рішення. Ні, prey_splitні explodeїх не слід використовувати для отримання структурованих фрагментів рядків. Це як прицілитися до мухи з базукою .
Maciej Sz

1
Якщо ви перевірите використання пам'яті під час роботи програми, ви побачите магію. Він насправді витягує файл, який ви читаєте, в пам'ять у випадку, якщо ви переходите через кожен рядок, і він зберігає ваше маркерне місце. Ви хочете, щоб це було по-справжньому ефективною пам'яттю. php.net/strtok#103051
AbsoluteƵERØ

2
швидка примітка, використання strtok()чогось іншого всередині цього whileциклу порушить речі. Я також використовував це, щоб схопити все в рядку аж до першого місця ( stackoverflow.com/a/2477411/1767412 ), і мені знадобилося хвилину, щоб зрозуміти, чому все йшло не так, як планували
billynoah

1
має бути прийнятою відповіддю, мабуть, найшвидшим рішенням з усіх варіантів.
Іван

94

Якщо вам потрібно обробляти нові рядки в різних системах, ви можете просто використовувати конфігурацію PHP_EOL (http://php.net/manual/en/reserved.constants.php), визначену PHP, і просто використовувати explode, щоб уникнути накладних витрат двигуна регулярних виразів .

$lines = explode(PHP_EOL, $subject);

30
Остерігайтеся: вона буде працювати в різних системах, але вона не буде добре працювати з рядками з різних систем . У Посібнику PHP зазначено, що PHP_EOL (string)це правильний символ 'End Of Line' для цієї платформи.
wadim

@wadim має рацію! Якщо ви обробляєте текстовий файл Windows на сервері Unix, це не вдасться.
javsmo

1
Будьте уважні, що залежно від довжини ваших ліній, це може споживати дуже велику кількість пам'яті для великих струн.
Синхро

Зауважте, що якщо останній рядок містить термінатор рядка, він також повертає ще один порожній рядок після цього.
праворуч

20

Це занадто складно і некрасиво, але, на мою думку, це шлях:

$fp = fopen("php://memory", 'r+');
fputs($fp, $data);
rewind($fp);
while($line = fgets($fp)){
  // deal with $line
}
fclose($fp);

1
+1, а також ви можете використовувати php://tempдля зберігання більших даних у тимчасовому файлі диска.
CodeAngry

4
Слід зазначити, що це дозволяє виявляти порожні рядки, на відміну від рішення strtok (). Документація знаходиться на веб-
Йосип Роден

7
foreach(preg_split('~[\r\n]+~', $text) as $line){
    if(empty($line) or ctype_space($line)) continue; // skip only spaces
    // if(!strlen($line = trim($line))) continue; // or trim by force and skip empty
    // $line is trimmed and nice here so use it
}

^ ось так ви правильно розбиваєте лінії , сумісні з платформою Regexp:)


6

Потенційні проблеми з пам'яттю для strtok:

Оскільки одне із запропонованих рішень використовує strtok, на жаль, воно не вказує на потенційну проблему пам'яті (хоча вона стверджує, що вона є ефективною пам'яті). Під час використання strtokвідповідно до посібника :

Зауважте, що лише перший виклик strtok використовує аргумент рядка. Кожен наступний виклик strtok потребує використання лише маркера, оскільки він відстежує, де він знаходиться у поточній рядку.

Це робиться, завантажуючи файл у пам'ять. Якщо ви використовуєте великі файли, вам потрібно їх перепрасувати, якщо ви переглядаєте файл.

<?php
function process($str) {
    $line = strtok($str, PHP_EOL);

    /*do something with the first line here...*/

    while ($line !== FALSE) {
        // get the next line
        $line = strtok(PHP_EOL);

        /*do something with the rest of the lines here...*/

    }
    //the bit that frees up memory
    strtok('', '');
}

Якщо ви стосуєтесь лише фізичних файлів (наприклад, передачі даних):

Згідно з посібником , для частини завантаження файлів ви можете використовувати fileкоманду:

 //Create the array
 $lines = file( $some_file );

 foreach ( $lines as $line ) {
   //do something here.
 }

4

Відповідь Кирила найкраще враховуючи, що вам потрібно вміти обробляти нові рядки на різних машинах.

"Я в основному шукаю корисні функції PHP, а не алгоритм, як це зробити. Будь-які пропозиції?"

Я їх дуже багато використовую:

  • explode () може використовуватися для розділення рядка на масив із заданим роздільником.
  • implode () - це аналог вибуху для переходу з масиву назад до рядка.
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.