Zig splitScalar: เก็บช่องว่างใน CSV เล็ก ๆ ไม่ให้ข้อมูลเลื่อน
ข้อมูลคั่นด้วย comma บางชุดให้ความหมายกับช่องว่าง เช่น room,,ready หมายถึง field กลางไม่มีค่า หากใช้ tokenizer ที่ข้ามช่องว่าง ตำแหน่งจะเลื่อน std.mem.splitScalar คืน field ว่างตามจริงและไม่ allocate สำเนาครับ
WHY: เก้าอี้ว่างก็ยังเป็นตำแหน่งหนึ่ง
เหมือนโต๊ะสามที่นั่ง แม้ที่กลางไม่มีคนนั่ง เราก็ไม่ควรเลื่อนคนที่สามมาเป็นคนที่สอง splitScalar เหมาะกับ schema ที่จำนวนและตำแหน่ง field สำคัญ ต่างจาก tokenizeScalar ซึ่งตั้งใจข้าม delimiter ซ้ำ
const std = @import("std");
fn parseThree(input: []const u8) ![3][]const u8 {
var it = std.mem.splitScalar(u8, input, ',');
const first = it.next() orelse return error.MissingField;
const second = it.next() orelse return error.MissingField;
const third = it.next() orelse return error.MissingField;
if (it.next() != null) return error.TooManyFields;
return .{ first, second, third };
}
test "split keeps an empty field" {
const fields = try parseThree("room,,ready");
try std.testing.expectEqualStrings("room", fields[0]);
try std.testing.expectEqualStrings("", fields[1]);
try std.testing.expectEqualStrings("ready", fields[2]);
try std.testing.expectError(error.MissingField, parseThree("room,ready"));
try std.testing.expectError(error.TooManyFields, parseThree("a,b,c,d"));
}
HOW: นี่ไม่ใช่ CSV parser เต็มรูปแบบ
ตัวอย่างใช้ delimiter หนึ่งไบต์และไม่รองรับ quoted field, comma ภายในข้อความ หรือ escape rule ของ CSV จริง หากรูปแบบข้อมูลอนุญาตสิ่งเหล่านั้น ให้ใช้ parser ที่เข้าใจมาตรฐานแทน อย่าค่อย ๆ เติมเงื่อนไขจนกลายเป็น parser ทำเองที่ไม่มี test ครบ
slice ที่ iterator คืนยังอ้าง input ต้นฉบับ จึงต้องรักษา buffer ไว้ตลอดช่วงใช้งาน กำหนดเพดานความยาว input และจำนวน field ก่อนนำไปสร้าง object เพื่อป้องกัน payload ใหญ่กินหน่วยความจำขั้นต่อไป
Recap
- ใช้
splitScalarเมื่อ empty field มีความหมาย - ใช้
tokenizeScalarเมื่อ delimiter ซ้ำควรถูกข้าม - ตรวจจำนวน field ทั้งขาดและเกิน
- อย่าใช้เป็น CSV parser ถ้ามี quote หรือ escape
- รักษาอายุ input buffer ให้ยาวกว่า slices