Computer >> คอมพิวเตอร์ >  >> การเขียนโปรแกรม >> Ruby

ทำความสะอาดสายทับทิมเร็วขึ้น 13 เท่า

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

บ่อยครั้งที่วิธีการที่คุณพิมพ์โดยอัตโนมัติเป็นวิธี Ruby ทั่วไปมากที่สุด เนื่องจากเป็นวิธีที่เราอ่านและเขียนมากกว่าวิธีอื่นๆ เช่น #gsub เป็นวิธีการทั่วไปในการแทนที่อักขระในสตริง แต่ Ruby มีอะไรอีกมากมายที่จะนำเสนอ พร้อมด้วยวิธีการอำนวยความสะดวกเฉพาะทางสำหรับการปฏิบัติงานมาตรฐาน

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

สี่วิธีในการลบ Spaces

นี่คือสตริงที่แสดงถึงหมายเลขบัตรเครดิต:"055 444 285" ในการทำงานกับมัน เราต้องการลบช่องว่าง #gsub สามารถทำได้; ด้วย #gsub คุณสามารถแทนที่อะไรก็ได้กับทุกอย่าง แต่มีตัวเลือกอื่น

string = "055 444 285"
string.gsub(/ /, '')
string.gsub(' ', '')
string.tr(' ', '')
string.delete(' ')
 
# => "055444285"

เป็นการแสดงออกที่ฉันชอบมากที่สุดเกี่ยวกับวิธีการอำนวยความสะดวก อันสุดท้ายเป็นตัวอย่างที่ดีของสิ่งนี้:ไม่ได้ชัดเจนไปกว่า "การลบช่องว่าง" เมื่อนึกถึงการประนีประนอมระหว่างตัวเลือกต่างๆ ความสามารถในการอ่านเป็นสิ่งสำคัญอันดับแรกของฉัน เว้นแต่แน่นอนว่าจะทำให้เกิดปัญหาด้านประสิทธิภาพ มาดูกันดีกว่าว่าวิธีแก้ปัญหาที่ฉันโปรดปราน #delete สาเหตุจริงๆ

ฉันเปรียบเทียบตัวอย่างข้างต้น คุณคิดว่าวิธีใดเร็วที่สุด

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
เดาลำดับจากมากไปน้อย เปิดสวิตช์เพื่อดูผลลัพธ์
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 ไม่เพียงแต่ช้าลงเท่านั้น แต่ยังต้องใช้ความพยายามเป็นพิเศษสำหรับผู้อ่านในการ 'ถอดรหัส' อาร์กิวเมนต์ มาดูกันว่าการเปรียบเทียบนี้จะออกมาเป็นอย่างไรเมื่อต้องทำความสะอาดมากกว่าแค่ช่องว่าง

เลือกหมายเลขของคุณ

ใช้หมายเลขโทรศัพท์ต่อไปนี้:'(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
อีกครั้ง เดาลำดับ จากนั้นเปิดสวิตช์เพื่อดูคำตอบ
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 ทำให้สิ่งต่างๆ ช้าลง ไม่น่าแปลกใจเลย และความตั้งใจเผยความชัดเจนของ #scan ค่าใช้จ่ายเราอย่างสุดซึ้ง แต่เมื่อดูวิธีที่ Ruby เชี่ยวชาญในการทำความสะอาด ทำให้ฉันได้ลิ้มลองมากขึ้น

เรื่องเงิน

ลองใช้วิธีลบสตริงย่อย "€ " จากสตริง "€ 300" . โซลูชันบางส่วนต่อไปนี้ระบุสตริงย่อยที่แน่นอน "€ " บางส่วนจะลบสัญลักษณ์สกุลเงินทั้งหมดหรืออักขระที่ไม่ใช่ตัวเลขทั้งหมดออก

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

คุณอาจคาดหวังและถูกต้องว่าผู้ชนะคือหนึ่งใน #delete ส. แต่อันไหนใน #delete ตัวแปรที่คุณคาดว่าจะเร็วที่สุด? บวก:วิธีอื่นเร็วกว่าวิธี #delete ส. อันไหน?

เดาแล้วเปิดเลย
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

ฉันรู้สึกประหลาดใจที่แม้แต่การแบ่งอาร์เรย์ก็เร็วกว่า #gsub และฉันยินดีเสมอที่ได้เห็น #split . เร็วแค่ไหน เป็น. และโปรดทราบว่าการลบที่ไม่ใช่ตัวเลขทั้งหมดนั้นเร็วกว่าการลบสตริงย่อยที่ระบุ

ติดตามเงิน

มาลบสกุลเงินหลังตัวเลขกัน (ฉันข้าม #gsub . ที่ช้ากว่า แบบต่างๆ)

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

มีการเสมอกันระหว่างผู้ชนะ 2 ตัวไหนที่คุณคาดหวังว่าจะแข่งกันให้เร็วที่สุด

และ:เดาว่า `#gsub' ช้ากว่า `#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

ไม่มีวิธีการเฉพาะที่เหมาะกับความต้องการของคุณเสมอไป คุณไม่สามารถใช้ #to_i หากคุณต้องการเก็บ "0" นำหน้า และ #delete_suffix อาศัยสมมติฐานที่ว่าสกุลเงินเป็นดอลลาร์สหรัฐฯ เป็นหลัก

วิธีการพิเศษเป็นเหมือนเครื่องมือความแม่นยำ—เหมาะสำหรับงานเฉพาะในบริบทเฉพาะ ดังนั้นจะมีบางกรณีที่ #gsub คือสิ่งที่เราต้องการ ใช้งานได้หลากหลายและอยู่ในใจเสมอ แต่การประมวลผลอาจยากขึ้นเล็กน้อย และมักจะช้ากว่า แม้จะช้ากว่าที่ฉันคาดไว้ สำหรับฉันแล้ว ความร่ำรวยของ Ruby ก็เป็นหนึ่งในเหตุผลที่ทำให้การทำงานด้วยสนุกมาก การชนะด้วยความเร็วเป็นโบนัสที่ดี