Ітерація над кожним рядком у рядку 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.