วนซ้ำแต่ละบรรทัดในสตริงใน PHP


130

ฉันมีแบบฟอร์มที่อนุญาตให้ผู้ใช้อัปโหลดไฟล์ข้อความหรือคัดลอก / วางเนื้อหาของไฟล์ลงในพื้นที่ข้อความ ฉันสามารถแยกความแตกต่างระหว่างทั้งสองได้อย่างง่ายดายและใส่สิ่งที่พวกเขาป้อนลงในตัวแปรสตริง แต่ฉันจะไปจากที่นั่นได้อย่างไร

ฉันจำเป็นต้องวนซ้ำในแต่ละบรรทัดของสตริง (ไม่ควรกังวลเกี่ยวกับการขึ้นบรรทัดใหม่บนเครื่องต่างๆ) ตรวจสอบให้แน่ใจว่ามีโทเค็นเดียว (ไม่มีช่องว่างแท็บเครื่องหมายจุลภาค ฯลฯ ) ล้างข้อมูลจากนั้นสร้างแบบสอบถาม SQL โดยอิงจากบรรทัดทั้งหมด

ฉันเป็นโปรแกรมเมอร์ที่ค่อนข้างเก่งดังนั้นฉันจึงรู้แนวคิดทั่วไปเกี่ยวกับวิธีการทำ แต่มันก็นานมากแล้วที่ฉันทำงานกับ PHP ฉันรู้สึกว่าฉันกำลังค้นหาสิ่งที่ผิดและทำให้ได้ข้อมูลที่ไร้ประโยชน์ ปัญหาสำคัญที่ฉันพบคือฉันต้องการอ่านเนื้อหาของสตริงทีละบรรทัด ถ้าเป็นไฟล์ก็จะง่าย

ส่วนใหญ่ฉันกำลังมองหาฟังก์ชัน PHP ที่มีประโยชน์ไม่ใช่อัลกอริทึมสำหรับวิธีการทำ ข้อเสนอแนะใด ๆ ?


คุณอาจต้องทำให้บรรทัดใหม่เป็นปกติก่อน วิธีs($myString)->normalizeLineEndings()นี้สามารถใช้ได้กับgithub.com/delight-im/PHP-Str (ไลบรารีภายใต้ใบอนุญาต MIT) ซึ่งมีตัวช่วยสตริงที่มีประโยชน์อื่น ๆ อีกมากมาย คุณอาจต้องการดูซอร์สโค้ด
caw

คำตอบ:


190

preg_split ตัวแปรที่มีข้อความและวนซ้ำบนอาร์เรย์ที่ส่งคืน:

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

สิ่งนี้จะจัดการ ^ M นอกเหนือจาก \ n \ r หรือไม่
Topher Fangio

ฉันไม่แน่ใจว่าการคืนค่าการขนส่ง ascii ถูกแปลงเป็น \ r หรือไม่เมื่อวางไว้ในตัวแปร หากไม่เป็นเช่นนั้นคุณสามารถใช้การแบ่ง () / exlope () ที่มีค่า ascii แทนได้เสมอ - ch (13)
Kyril

12
regexp /((\r?\n)|(\r\n?))/ที่ดีกว่าคือ
Félix Saparelli

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,000 บรรทัด: 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 ค่าคงที่ที่กำหนดไว้ล่วงหน้าของ PHP (http://php.net/manual/en/reserved.constants.php) และใช้ Explode เพื่อหลีกเลี่ยงค่าใช้จ่ายของเอ็นจิ้นนิพจน์ทั่วไป .

$lines = explode(PHP_EOL, $subject);

30
ระวัง: มันจะทำงานบนระบบที่แตกต่างกันแต่มันจะไม่ทำงานได้ดีกับสตริงจากระบบที่แตกต่างกัน PHP คู่มือระบุว่าPHP_EOL (string)เป็นถูกต้อง 'จุดสิ้นสุดของบรรทัด' สัญลักษณ์สำหรับนี้แพลตฟอร์ม
wadim

@wadim ถูก! หากคุณกำลังประมวลผลไฟล์ข้อความ Windows บนเซิร์ฟเวอร์ Unix ไฟล์นั้นจะล้มเหลว
javsmo

1
ระวังว่าขึ้นอยู่กับความยาวของบรรทัดของคุณสิ่งนี้อาจกินหน่วยความจำจำนวนมากสำหรับสตริงขนาดใหญ่
Synchro

โปรดทราบว่าหากบรรทัดสุดท้ายมีตัวยุติบรรทัดสิ่งนี้จะส่งคืนสตริงว่างอีกชุดหนึ่งหลังจากนั้น
rightfold

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 () เอกสารประกอบอยู่ที่php.net/manual/en/…
Josip Rodin

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('', '');
}

หากคุณกังวลเฉพาะกับไฟล์ฟิสิคัล (เช่น datamining):

ตามคู่มือสำหรับส่วนการอัปโหลดไฟล์คุณสามารถใช้fileคำสั่ง:

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

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

4

คำตอบของ Kyril ดีที่สุดเมื่อพิจารณาว่าคุณต้องสามารถจัดการกับข่าวใหม่บนเครื่องต่างๆได้

"ส่วนใหญ่ฉันกำลังมองหาฟังก์ชัน PHP ที่เป็นประโยชน์ไม่ใช่อัลกอริทึมสำหรับวิธีการทำงานมีข้อเสนอแนะใด ๆ "

ฉันใช้สิ่งเหล่านี้มาก:

  • Explode ()สามารถใช้เพื่อแบ่งสตริงออกเป็นอาร์เรย์โดยให้ตัวคั่นเดียว
  • Implode () เป็นคู่ของ Explode เพื่อเปลี่ยนจากอาร์เรย์กลับไปที่สตริง
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.