เมื่อแปลความคิดเป็นโค้ด คุณส่วนใหญ่ใช้เมธอดที่คุ้นเคยที่สุด นี่คือเมธอดที่อยู่ top of mind และมาอัตโนมัติ: คุณเห็นสตริงที่ต้องทำความสะอาด และนิ้วของคุณพิมพ์เมธอดที่จะให้ผลลัพธ์
บ่อยครั้ง เมธอดที่คุณพิมพ์อัตโนมัติคือเมธอด Ruby ทั่วไปที่สุด เพราะเป็นเมธอดที่เราอ่านและเขียนมากกว่าอื่นๆ เช่น #gsub เป็น generic method สำหรับแทนที่ characters ในสตริง แต่ Ruby มีอะไรมากกว่านั้น มากมาย ด้วย convenience methods ที่เฉพาะเจาะจงสำหรับ operations มาตรฐาน
ผมชอบ rich idiom ของ Ruby มากที่สุดเพราะมันทำให้โค้ดสง่างามและอ่านง่ายกว่า หากเราอยากได้ประโยชน์จากความหลากหลายนี้ เราต้องใช้เวลา refactor แม้ส่วนที่ง่ายที่สุดของโค้ดเรา—เช่น การทำความสะอาดสตริง—and มันต้องใช้ความพยายามเล็กน้อยเพื่อขยาย vocabulary คำถามคือ: ความพยายามเพิ่มเติมนั้นคุ้มค่าหรือไม่?
สี่วิธีในการลบ spaces
นี่คือสตริงที่แทนหมายเลขบัตรเครดิต: "055 444 285" เพื่อทำงานกับมัน เราต้องการลบ spaces #gsub ทำได้ ด้วย #gsub คุณสามารถแทนที่อะไรก็ได้ด้วยทุกอย่าง แต่มีตัวเลือกอื่น
string = "055 444 285" string.gsub(/ /, '') string.gsub(' ', '') string.tr(' ', '') string.delete(' ') # => "055444285"มันคือ expressiveness ที่ผมชอบมากที่สุดใน convenience methods ตัวสุดท้ายเป็นตัวอย่างดี: ไม่ได้อะไรชัดเจนกว่า "delete spaces" ถึง trade-offs ระหว่างตัวเลือก readability เป็น first priority ของผม เว้นแต่มันก่อให้เกิดปัญหาประสิทธิภาพ ดังนั้น มาดูว่า #delete ที่เป็นโปรดของผมก่อให้เกิดความเจ็บปวดมากน้อยเพียงใด
ผม benchmark ตัวอย่างข้างต้น คุณคิดว่าวิธีไหนเร็วที่สุด?
Benchmark.ips do |x| x.config(time: 30, warmup: 2) x.report('gsub') { string.gsub(/ /, '') } x.report('gsub, no regex') { string.gsub(' ', '') } x.report('tr') { string.tr(' ','') } x.report('delete') { string.delete(' ') } x.compare! endทายลำดับจากเร็วสุดไปช้าสุด เปิด toggle เพื่อดูผลลัพธ์
Comparison: delete: 2326817.5 i/s tr: 2121629.8 i/s - 1.10x slower gsub, no regex: 868184.1 i/s - 2.68x slower gsub: 474970.5 i/s - 4.90x slower ผมไม่แปลกใจกับลำดับ แต่ความแตกต่างของความเร็วยังทำให้ผมประหลาดใจ #gsub ไม่ได้แค่ช้ากว่า แต่ยังต้องใช้ความพยายามเพิ่มสำหรับผู้อ่านในการ 'decode' อาร์กิวเมนต์ มาดูว่าการเปรียบเทียบนี้เป็นอย่างไรเมื่อทำความสะอาดมากกแค่ spaces
เลือกตัวเลขของคุณ (Pick Your Numbers)
รับหมายเลขโทรศัพท์นี้: '(408) 974-2414' สมมติว่าเราต้องการแค่ตัวเลข => 4089742414 ผมเพิ่ม #scan เพราะผมชอบที่มันแสดงเจตนาชัดเจนว่ามุ่งมั่นหาบางสิ่งบางอย่าง แทนที่จะพยายามลบทุกอย่างที่เราไม่ต้องการ
Benchmark.ips do |x| x.config(time: 30, warmup: 2) x.report ('gsub') { string.gsub(/[^0-9] /, '') } x.report('tr') { string.tr("^0-9", "") } x.report('delete_chars') { string.delete("^0-9") } x.report('scan') { string.scan(/[0-9]/).join } x.compare! endอีกครั้ง ทายลำดับ แล้วเปิด toggle ดูคำตอบ
Comparison: delete_chars: 2006750.8 i/s tr: 1856429.0 i/s - 1.08x slower gsub: 523174.7 i/s - 3.84x slower scan: 227717.4 i/s - 8.81x slower การใช้ regex ทำให้ช้าลง น่าประหลาดใจ ไม่ได้ และ intention-revealing expressiveness ของ #scan ใช้ค่าใช้จ่ายสูง แต่การดูว่า specialized methods ของ Ruby จัดการการทำความสะอาดอย่างไร ให้ผมรสชาติอยากลองอีก
เรื่องของเงิน (On the Money)
ลองวิธีบางอย่างในการลบ substring "€ " จากสตริง "€ 300" บางโซลูชันระบุ substring ที่แน่นอน "€ " บางอย่างจะลบ currency symbols ทั้งหมดหรือ non-numerical characters ทั้งหมด
Benchmark.ips do |x| x.config(time: 30, warmup: 2) x.report('delete specific chars') { string.delete("€ ") } x.report('delete non-numericals') { string.delete("^0-9") } x.report('delete prefix') { string.delete_prefix("€ ") } x.report('delete prefix, strip') { string.delete_prefix("€").strip } x.report('gsub') { string.gsub(/€ /, '') } x.report('gsub-non-nums') { string.gsub(/[^0-9]/, '') } x.report('tr') { string.tr("€ ", "") } x.report('slice array') { string.chars.slice(2..-1).join } x.report('split') { string.split.last } x.report('scan nums') { string.scan(/\d/).join } x.compare! endคุณอาจคาดหวัง และถูกต้องที่ว่า winner เป็นหนึ่งใน #deletes แต่คุณคาดหวังว่าตัวแปรของ #delete ไหนเร็วที่สุด? บวก: วิธีอื่นหนึ่งเร็วกว่าบาง #deletes อันไหน?
ทายแล้วเปิดดู
Comparison: delete prefix: 4236218.6 i/s delete prefix, strip: 3116439.6 i/s - 1.36x slower split: 2139602.2 i/s - 1.98x slower delete non-numericals: 1949754.0 i/s - 2.17x slower delete specific chars: 1045651.9 i/s - 4.05x slower tr: 951352.0 i/s - 4.45x slower slice array: 681196.2 i/s - 6.22x slower gsub: 548588.3 i/s - 7.72x slower gsub-non-nums: 489744.8 i/s - 8.65x slower scan nums: 418978.8 i/s - 10.11x slower ผมประหลาดใจที่แม้การ slice array ก็เร็วกว่า #gsub และผมยินดีเสมอที่เห็น #split เร็วแค่ไหน และสังเกตว่าการลบ non-numericals ทั้งหมดเร็วกว่าการลบ substring เฉพาะเจาะจง
ตามเงิน (Follow the Money)
มาลบ currency หลังตัวเลข (ผมข้าม #gsub variants ที่ช้ากว่า)
Benchmark.ips do |x| x.config(time: 30, warmup: 2) x.report('gsub') { string.gsub(/ USD/, '') x.report('tr') { string.tr(" USD", "") } x.report('delete_chars') { string.delete("^0-9") x.report('delete_suffix') { string.delete_suffix(" USD") } x.report('to_i.to_s') { string.to_i.to_s } x.report("split") { string.split.first } x.compare! endมี draw ระหว่าง winners สองอันไหนคุณคาดหวังจะแข่งกันเป็นอันดับเร็วสุด?
และ: ทายว่า #gsub ช้ากว่าเท่าไหร่ที่นี่
Comparison: delete_suffix: 4354205.4 i/s to_i.to_s: 4307614.6 i/s - same-ish: difference falls within error split: 2870187.8 i/s - 1.52x slower delete_chars: 1989566.1 i/s - 2.19x slower tr: 1853957.1 i/s - 2.35x slower gsub: 524080.6 i/s - 13.22x slower ไม่มี specialized method ที่จะตอบสนองความต้องการของคุณเสมอไป คุณไม่สามารถใช้ #to_i หากคุณต้องการเก็บ leading "0" ไว้ และ #delete_suffix พึ่งพาสมมติฐานมากว่าคurrency เป็น US Dollars
Specialized methods เหมือน precision tools—เหมาะกับงานเฉพาะในบริบทเฉพาะ ดังนั้นจะมีกรณีที่ #gsub คือสิ่งที่เราต้องการพอดี มัน versatile และอยู่ top of mind เสมอ แต่มันอาจยากต่อการประมวลผลหน่อยและช้ากว่าบ่อยครั้ง แม้ช้ากว่าที่ผมคาดหวัง ด้วยความหลากหลายของ Ruby ก็เป็นหนึ่งในเหตุผลที่ทำให้มันสนุกมาก ความเร็วที่ชนะเป็น bonus ที่ดี