Go strings.SplitAfterSeq: แบ่งข้อความแบบ iterator โดยเก็บตัวคั่นไว้
ถ้าต้องแยก log ก้อนใหญ่แล้วใช้เพียงไม่กี่ record การเรียก strings.SplitAfter จะสร้าง slice ที่ถือทุกส่วนไว้พร้อมกันครับ strings.SplitAfterSeq คืน iter.Seq[string] ให้ consumer รับทีละส่วน และยังเก็บ separator ไว้ท้ายส่วนเหมือน SplitAfter
WHY: ดึงทีละใบ แทนเทเอกสารทั้งกล่อง
ลองนึกถึงม้วนใบเสร็จที่มีเส้นปรุ เราอาจฉีกทีละใบจนพบรายการที่ต้องการ ไม่จำเป็นต้องฉีกทั้งม้วนแล้ววางเต็มโต๊ะ Iterator ช่วยลด slice ของผลลัพธ์และเปิดทางให้หยุดเร็ว แต่ substring ที่ได้ยังอ้างข้อมูลจาก string ต้นฉบับ จึงไม่ได้แปลว่าหน่วยความจำของ input ใหญ่จะถูกปล่อยทันที
package main
import (
"slices"
"strings"
"testing"
)
func firstTwoRecords(input string) []string {
parts := make([]string, 0, 2)
for part := range strings.SplitAfterSeq(input, "\n") {
parts = append(parts, part)
if len(parts) == 2 {
break
}
}
return parts
}
func TestSplitAfterSeq(t *testing.T) {
got := firstTwoRecords("alpha\nbeta\ngamma\n")
if !slices.Equal(got, []string{"alpha\n", "beta\n"}) {
t.Fatalf("unexpected records: %#v", got)
}
all := slices.Collect(strings.SplitAfterSeq("ab", ""))
if !slices.Equal(all, []string{"a", "b"}) {
t.Fatalf("empty separator must split after UTF-8 sequences: %#v", all)
}
}
ขอบเขตที่ต้องรู้
Iterator เป็น single-use: อย่าเก็บไว้แล้วหวังวนซ้ำเหมือน slice หากต้องใช้หลายรอบให้สร้าง SplitAfterSeq ใหม่ หรือ slices.Collect เมื่อข้อมูลเล็กและต้อง random access จริง ๆ ส่วน separator ว่างจะแบ่งหลัง UTF-8 sequence แต่ละตัว ไม่ใช่หลังทุก byte
ถ้าต้องเก็บผลลัพธ์เล็กไว้นานกว่าข้อความต้นฉบับก้อนใหญ่มาก ให้พิจารณา strings.Clone(part) ตรง boundary เพื่อตัด ownership ให้ชัด อย่า clone ทุกชิ้นโดยอัตโนมัติ เพราะจะลบประโยชน์ด้าน allocation ไปเอง
Recap
- ใช้
SplitAfterSeqเมื่ออ่านตามลำดับและอาจหยุดก่อนจบ - ใช้
SplitSeqหากไม่ต้องเก็บตัวคั่น - จำว่า iterator ใช้ครั้งเดียวและ substring ยังผูกกับ input
- collect หรือ clone เฉพาะเมื่อ ownership และการเข้าถึงต้องการจริง