Tôi đang tìm kiếm một cách nối dây thanh lịch hơn trong Ruby.
Tôi có dòng sau:
source = "#{ROOT_DIR}/" << project << "/App.config"
Có cách nào đẹp hơn để làm điều này?
Và đối với vấn đề đó, sự khác biệt giữa <<
và là +
gì?
Tôi đang tìm kiếm một cách nối dây thanh lịch hơn trong Ruby.
Tôi có dòng sau:
source = "#{ROOT_DIR}/" << project << "/App.config"
Có cách nào đẹp hơn để làm điều này?
Và đối với vấn đề đó, sự khác biệt giữa <<
và là +
gì?
Câu trả lời:
Bạn có thể làm điều đó theo nhiều cách:
<<
nhưng đó không phải là cách thông thườngVới phép nội suy chuỗi
source = "#{ROOT_DIR}/#{project}/App.config"
với +
source = "#{ROOT_DIR}/" + project + "/App.config"
Phương pháp thứ hai dường như hiệu quả hơn về mặt bộ nhớ / tốc độ so với những gì tôi đã thấy (mặc dù không được đo). Tất cả ba phương thức sẽ đưa ra một lỗi không đổi chưa được khởi tạo khi ROOT_DIR không.
Khi xử lý tên đường dẫn, bạn có thể muốn sử dụng File.join
để tránh gây rối với dấu phân cách tên đường dẫn.
Cuối cùng, đó là một vấn đề của hương vị.
Các +
nhà điều hành là nối lựa chọn bình thường, và có lẽ là cách nhanh nhất để chuỗi concatenate.
Sự khác biệt giữa +
và <<
là <<
thay đổi đối tượng ở phía bên trái của nó, và +
không.
irb(main):001:0> s = 'a'
=> "a"
irb(main):002:0> s + 'b'
=> "ab"
irb(main):003:0> s
=> "a"
irb(main):004:0> s << 'b'
=> "ab"
irb(main):005:0> s
=> "ab"
+
và <<
sẽ giống nhau. Nếu bạn đang xử lý nhiều chuỗi hoặc thực sự lớn, thì bạn có thể nhận thấy sự khác biệt. Tôi đã ngạc nhiên bởi cách họ thực hiện tương tự. gist.github.com/2895311
5.times do ... end
khối) cho mỗi trình thông dịch, bạn sẽ có kết quả chính xác hơn. Thử nghiệm của tôi đã cho thấy phép nội suy là phương pháp nhanh nhất, trên tất cả các trình thông dịch Ruby. Tôi đã dự kiến sẽ <<
là nhanh nhất, nhưng đó là lý do tại sao chúng tôi điểm chuẩn.
từ http://greyblake.com/blog/2012/09/02/ruby-perfomance-tricks/
Sử dụng <<
aka concat
hiệu quả hơn nhiều so với +=
, vì cái sau tạo ra một đối tượng tạm thời và ghi đè lên đối tượng đầu tiên với đối tượng mới.
require 'benchmark'
N = 1000
BASIC_LENGTH = 10
5.times do |factor|
length = BASIC_LENGTH * (10 ** factor)
puts "_" * 60 + "\nLENGTH: #{length}"
Benchmark.bm(10, '+= VS <<') do |x|
concat_report = x.report("+=") do
str1 = ""
str2 = "s" * length
N.times { str1 += str2 }
end
modify_report = x.report("<<") do
str1 = "s"
str2 = "s" * length
N.times { str1 << str2 }
end
[concat_report / modify_report]
end
end
đầu ra:
____________________________________________________________
LENGTH: 10
user system total real
+= 0.000000 0.000000 0.000000 ( 0.004671)
<< 0.000000 0.000000 0.000000 ( 0.000176)
+= VS << NaN NaN NaN ( 26.508796)
____________________________________________________________
LENGTH: 100
user system total real
+= 0.020000 0.000000 0.020000 ( 0.022995)
<< 0.000000 0.000000 0.000000 ( 0.000226)
+= VS << Inf NaN NaN (101.845829)
____________________________________________________________
LENGTH: 1000
user system total real
+= 0.270000 0.120000 0.390000 ( 0.390888)
<< 0.000000 0.000000 0.000000 ( 0.001730)
+= VS << Inf Inf NaN (225.920077)
____________________________________________________________
LENGTH: 10000
user system total real
+= 3.660000 1.570000 5.230000 ( 5.233861)
<< 0.000000 0.010000 0.010000 ( 0.015099)
+= VS << Inf 157.000000 NaN (346.629692)
____________________________________________________________
LENGTH: 100000
user system total real
+= 31.270000 16.990000 48.260000 ( 48.328511)
<< 0.050000 0.050000 0.100000 ( 0.105993)
+= VS << 625.400000 339.800000 NaN (455.961373)
Vì đây là đường dẫn tôi có thể sử dụng mảng và tham gia:
source = [ROOT_DIR, project, 'App.config'] * '/'
Đây là một chuẩn mực khác lấy cảm hứng từ ý chính này . Nó so sánh phép nối ( +
), nối thêm ( <<
) và phép nội suy ( #{}
) cho các chuỗi động và được xác định trước.
require 'benchmark'
# we will need the CAPTION and FORMAT constants:
include Benchmark
count = 100_000
puts "Dynamic strings"
Benchmark.benchmark(CAPTION, 7, FORMAT) do |bm|
bm.report("concat") { count.times { 11.to_s + '/' + 12.to_s } }
bm.report("append") { count.times { 11.to_s << '/' << 12.to_s } }
bm.report("interp") { count.times { "#{11}/#{12}" } }
end
puts "\nPredefined strings"
s11 = "11"
s12 = "12"
Benchmark.benchmark(CAPTION, 7, FORMAT) do |bm|
bm.report("concat") { count.times { s11 + '/' + s12 } }
bm.report("append") { count.times { s11 << '/' << s12 } }
bm.report("interp") { count.times { "#{s11}/#{s12}" } }
end
đầu ra:
Dynamic strings
user system total real
concat 0.050000 0.000000 0.050000 ( 0.047770)
append 0.040000 0.000000 0.040000 ( 0.042724)
interp 0.050000 0.000000 0.050000 ( 0.051736)
Predefined strings
user system total real
concat 0.030000 0.000000 0.030000 ( 0.024888)
append 0.020000 0.000000 0.020000 ( 0.023373)
interp 3.160000 0.160000 3.320000 ( 3.311253)
Kết luận: nội suy trong MRI là nặng.
Tôi thích sử dụng Pathname:
require 'pathname' # pathname is in stdlib
Pathname(ROOT_DIR) + project + 'App.config'
về <<
và +
từ các tài liệu ruby:
+
: Trả về một Chuỗi mới chứa other_str được nối với str
<<
: Nối đối tượng đã cho vào str. Nếu đối tượng là Fixnum trong khoảng từ 0 đến 255, nó sẽ được chuyển đổi thành ký tự trước khi ghép.
Vì vậy, sự khác biệt là ở những gì trở thành toán hạng đầu tiên ( <<
thực hiện thay đổi tại chỗ, +
trả về chuỗi mới để bộ nhớ nặng hơn) và điều gì sẽ xảy ra nếu toán hạng đầu tiên là Fixnum ( <<
sẽ thêm như thể đó là ký tự có mã bằng số đó, +
sẽ tăng lỗi)
Pathname('/home/foo') + '/etc/passwd' # => #<Pathname:/etc/passwd>
. Điều này là do thiết kế, dựa trên ví dụ rubydoc. Có vẻ như File.join an toàn hơn.
(Pathname(ROOT_DIR) + project + 'App.config').to_s
nếu bạn muốn trả về một đối tượng chuỗi.
Hãy để tôi cho bạn thấy tất cả kinh nghiệm của tôi với điều đó.
Tôi đã có một truy vấn trả về 32k bản ghi, cho mỗi bản ghi tôi đã gọi một phương thức để định dạng bản ghi cơ sở dữ liệu đó thành một chuỗi được định dạng và nối chuỗi đó thành một Chuỗi mà ở cuối tất cả quá trình này sẽ biến thành một tệp trong đĩa.
Vấn đề của tôi là do hồ sơ đi, khoảng 24k, quá trình nối chuỗi đã bật lên một nỗi đau.
Tôi đã làm điều đó bằng cách sử dụng toán tử '+' thông thường.
Khi tôi đổi thành '<<' giống như phép thuật. Đã thực sự nhanh chóng.
Vì vậy, tôi đã nhớ lại thời xưa của mình - năm 1998 - khi tôi đang sử dụng Java và nối chuỗi bằng cách sử dụng '+' và thay đổi từ String thành StringBuffer (và bây giờ chúng tôi, nhà phát triển Java có StringBuilder).
Tôi tin rằng quá trình + / << trong thế giới Ruby giống như + / StringBuilder.append trong thế giới Java.
Việc đầu tiên phân bổ lại toàn bộ đối tượng trong bộ nhớ và đối tượng khác chỉ trỏ đến một địa chỉ mới.
Bạn nói gì? Thế còn #concat
phương pháp thì sao?
a = 'foo'
a.object_id #=> some number
a.concat 'bar' #=> foobar
a.object_id #=> same as before -- string a remains the same object
Trong tất cả các công bằng, concat
được đặt bí danh là <<
.
"foo" "bar" 'baz" #=> "foobarabaz"
Dưới đây là nhiều cách hơn để làm điều này:
"String1" + "String2"
"#{String1} #{String2}"
String1<<String2
Và như thế ...
Bạn có thể sử dụng +
hoặc <<
toán tử, nhưng trong .concat
chức năng ruby là thứ thích hợp nhất, vì nó nhanh hơn nhiều so với các toán tử khác. Bạn có thể sử dụng nó như thế nào.
source = "#{ROOT_DIR}/".concat(project.concat("/App.config"))
.
sau lần cuối concat
không?
Vấn đề tình hình, ví dụ:
# this will not work
output = ''
Users.all.each do |user|
output + "#{user.email}\n"
end
# the output will be ''
puts output
# this will do the job
output = ''
Users.all.each do |user|
output << "#{user.email}\n"
end
# will get the desired output
puts output
Trong ví dụ đầu tiên, nối với +
toán tử sẽ không cập nhật output
đối tượng, tuy nhiên, trong ví dụ thứ hai, <<
toán tử sẽ cập nhật output
đối tượng với mỗi lần lặp. Vì vậy, đối với các loại tình huống trên, <<
là tốt hơn.
Đối với trường hợp cụ thể của bạn, bạn cũng có thể sử dụng Array#join
khi xây dựng loại đường dẫn tệp của chuỗi:
string = [ROOT_DIR, project, 'App.config'].join('/')]
Điều này có tác dụng phụ dễ chịu là tự động chuyển đổi các loại khác nhau thành chuỗi:
['foo', :bar, 1].join('/')
=>"foo/bar/1"