Computer >> Máy Tính >  >> Lập trình >> Ruby

Dọn dẹp chuỗi Ruby nhanh hơn 13 lần

Khi dịch suy nghĩ của bạn thành mã, rất có thể, bạn sử dụng các phương pháp mà bạn quen thuộc nhất. Đây là những phương pháp quan tâm hàng đầu và tự động đến với bạn:bạn thấy một chuỗi cần dọn dẹp và ngón tay của bạn gõ các phương thức sẽ nhận được kết quả.

Thông thường, các phương thức bạn nhập tự động là các phương thức Ruby chung chung nhất, vì chúng là những phương thức mà chúng ta đọc và viết nhiều hơn những phương thức khác, ví dụ:#gsub là một phương thức chung để thay thế các ký tự trong chuỗi. Nhưng, Ruby có rất nhiều thứ để cung cấp, với nhiều phương pháp tiện lợi chuyên biệt hơn cho các hoạt động tiêu chuẩn.

Tôi yêu thích câu thành ngữ phong phú của Ruby chủ yếu vì nó làm cho mã trở nên thanh lịch và dễ đọc hơn. Nếu chúng ta muốn hưởng lợi từ sự phong phú này, chúng ta cần dành thời gian để cấu trúc lại ngay cả những phần đơn giản nhất của mã của chúng ta — ví dụ, dọn dẹp một chuỗi — và cần một chút nỗ lực để mở rộng vốn từ vựng của chúng ta. Câu hỏi đặt ra là:nỗ lực thêm có xứng đáng không?

Bốn cách để xóa dấu cách

Đây là chuỗi đại diện cho số thẻ tín dụng:"055 444 285". Để làm việc với nó, chúng tôi muốn xóa các khoảng trắng. #gsub có thể làm được điều này; với #gsub bạn có thể thay thế bất cứ thứ gì bằng mọi thứ. Nhưng có những lựa chọn khác.

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

Đó là sự thể hiện mà tôi thích nhất về các phương pháp tiện lợi. Cái cuối cùng là một ví dụ điển hình về điều này:nó không rõ ràng hơn là "xóa dấu cách". Suy nghĩ về sự cân bằng giữa các lựa chọn, tính dễ đọc là ưu tiên hàng đầu của tôi, trừ khi tất nhiên, nó gây ra các vấn đề về hiệu suất. Vì vậy, hãy xem giải pháp yêu thích của tôi, #delete nguyên nhân thực sự.

Tôi đã chuẩn hóa các ví dụ trên. Bạn nghĩ phương pháp nào trong số những phương pháp này là nhanh nhất?

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
Đoán thứ tự từ người có hiệu suất cao nhất đến kém nhất. Mở nút chuyển để xem kết quả
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

Tôi không ngạc nhiên về thứ tự, nhưng sự khác biệt về tốc độ vẫn khiến tôi ngạc nhiên. #gsub không chỉ chậm hơn, mà nó còn đòi hỏi người đọc phải nỗ lực nhiều hơn để 'giải mã' các lập luận. Hãy xem so sánh này hoạt động như thế nào khi dọn dẹp nhiều hơn không gian.

Chọn số của bạn

Lấy số điện thoại sau:'(408) 974-2414' . Giả sử chúng ta chỉ cần số => 4089742414 . Tôi đã thêm một #scan cũng như vì tôi thích điều đó thể hiện rõ ràng hơn rằng chúng tôi hướng tới một số điều cụ thể, thay vì cố gắng loại bỏ tất cả những thứ chúng tôi không muốn.

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
Một lần nữa, hãy đoán thứ tự, sau đó mở nút chuyển đổi để xem câu trả lời
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

Sử dụng regex làm chậm mọi thứ, điều đó không có gì đáng ngạc nhiên. Và ý định tiết lộ tính biểu cảm của #scan khiến chúng ta phải trả giá đắt. Nhưng khi nhìn vào cách các phương pháp chuyên biệt của Ruby xử lý việc dọn dẹp, tôi sẽ hiểu thêm.

Về tiền

Hãy thử một số cách xóa chuỗi con "€ " từ chuỗi "€ 300" . Một số giải pháp sau chỉ định chính xác chuỗi con "€ " , một số sẽ chỉ xóa tất cả các ký hiệu tiền tệ hoặc tất cả các ký tự không phải số.

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

Bạn có thể mong đợi và đúng như vậy, người chiến thắng là một trong những #delete S. Nhưng cái nào trong #delete biến thể bạn mong đợi là nhanh nhất? Thêm vào đó:một trong những phương pháp khác nhanh hơn một số phương pháp trong số #delete S. Cái nào?

Đoán và sau đó mở.
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

Tôi ngạc nhiên rằng ngay cả việc cắt một mảng cũng nhanh hơn #gsub và tôi luôn hài lòng khi thấy tốc độ của #split Là. Và lưu ý rằng xóa tất cả các số không phải là số nhanh hơn xóa một chuỗi con cụ thể.

Theo dõi tiền bạc

Hãy xóa đơn vị tiền tệ sau số. (Tôi đã bỏ qua #gsub chậm hơn các biến thể.)

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

Có một trận hòa giữa những người chiến thắng. Bạn mong đợi 2 người nào sẽ cạnh tranh để trở thành người nhanh nhất?

Và:đoán _ chậm hơn bao nhiêu `# gsub` ở đây.
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

Không phải lúc nào cũng có một phương pháp chuyên biệt phù hợp với nhu cầu của bạn. Bạn không thể sử dụng #to_i nếu bạn cần giữ số "0" ở đầu. Và #delete_suffix chủ yếu dựa vào giả định rằng đơn vị tiền tệ là Đô la Mỹ.

Các phương pháp chuyên biệt giống như các công cụ chính xác — phù hợp với một nhiệm vụ cụ thể trong bối cảnh cụ thể. Vì vậy, sẽ luôn có trường hợp #gsub là chính xác những gì chúng tôi cần. Nó rất linh hoạt và luôn được ưu tiên hàng đầu. Nhưng nó có thể khó xử lý hơn một chút và thường chậm hơn, thậm chí chậm hơn tôi mong đợi. Đối với tôi, sự giàu có của Ruby cũng là một trong những lý do khiến nó rất thú vị khi làm việc cùng. Chiến thắng tốc độ là một phần thưởng tuyệt vời.