Nối chuỗi trong Ruby


364

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ì?


3
Câu hỏi stackoverflow.com/questions/4684446/ này có liên quan cao.
Mắt

<< đây là cách hiệu quả hơn để thực hiện ghép.
Taimoor Changaiz

Câu trả lời:


574

Bạn có thể làm điều đó theo nhiều cách:

  1. Như bạn đã chỉ ra <<nhưng đó không phải là cách thông thường
  2. Với phép nội suy chuỗi

    source = "#{ROOT_DIR}/#{project}/App.config"
  3. 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ị.


7
Tôi không có nhiều kinh nghiệm với ruby. Nhưng nhìn chung trong trường hợp bạn nối nhiều chuỗi, bạn thường có thể đạt được hiệu suất bằng cách nối các chuỗi vào một mảng và sau đó kết thúc chuỗi đó lại với nhau theo nguyên tử. Vậy thì << có thể hữu ích?
PEZ

1
Bạn sẽ phải thêm bộ nhớ một bản sao chuỗi dài hơn vào nó. << ít nhiều giống với + ngoại trừ việc bạn có thể << với một ký tự.
Keltia

9
Thay vì sử dụng << trên các phần tử của một mảng, hãy sử dụng phép nối Array #, nó nhanh hơn nhiều.
Cấp Hutchins

94

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 +<<<<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"

32
Toán tử + chắc chắn không phải là cách nhanh nhất để nối chuỗi. Mỗi khi bạn sử dụng nó, nó tạo ra một bản sao, trong khi << ghép nối tại chỗ và hiệu quả hơn nhiều.
Evil Trout

5
Đối với hầu hết các sử dụng, nội suy, +<<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
Matt Burke

8
Kết quả jruby của bạn bị sai lệch so với phép nội suy do quá tải JVM chạy sớm. Nếu bạn chạy bộ kiểm tra nhiều lần (trong cùng một quy trình - vì vậy hãy gói mọi thứ trong một 5.times do ... endkhố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.
womble

Không quá thành thạo về Ruby, tôi tò mò liệu đột biến được thực hiện trên stack hay heap? Nếu trên một đống, ngay cả một hoạt động đột biến, có vẻ như nó sẽ nhanh hơn, có thể liên quan đến một số hình thức của malloc. Không có nó, tôi mong đợi một bộ đệm tràn. Sử dụng ngăn xếp có thể khá nhanh nhưng giá trị kết quả có thể được đặt trên heap dù sao, đòi hỏi một hoạt động malloc. Cuối cùng, tôi hy vọng con trỏ bộ nhớ là một địa chỉ mới, ngay cả khi tham chiếu biến làm cho nó trông giống như một đột biến tại chỗ. Vì vậy, thực sự, có một sự khác biệt?
Robin Coe

79

Nếu bạn chỉ nối các đường dẫn, bạn có thể sử dụng phương thức File.join của Ruby.

source = File.join(ROOT_DIR, project, 'App.config')

5
Đây có vẻ là cách để đi kể từ đó ruby ​​sẽ đảm nhiệm việc tạo chuỗi chính xác trên hệ thống với các dấu phân cách đường dẫn khác nhau.
PEZ

26

từ http://greyblake.com/blog/2012/09/02/ruby-perfomance-tricks/

Sử dụng <<aka concathiệ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)

11

Vì đây là đường dẫn tôi có thể sử dụng mảng và tham gia:

source = [ROOT_DIR, project, 'App.config'] * '/'

9

Đâ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.


Vì các chuỗi bắt đầu trở nên bất biến bây giờ, tôi rất muốn thấy một chuẩn mực mới cho điều này.
bibstha

7

Tôi thích sử dụng Pathname:

require 'pathname' # pathname is in stdlib
Pathname(ROOT_DIR) + project + 'App.config'

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)


2
Tôi mới phát hiện ra rằng việc gọi '+' trên Tên đường dẫn có thể nguy hiểm vì nếu đối số là đường dẫn tuyệt đối, đường dẫn người nhận bị bỏ qua : 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.
Kelvin

bạn cũng cần gọi (Pathname(ROOT_DIR) + project + 'App.config').to_snếu bạn muốn trả về một đối tượng chuỗi.
lacostenycoder

6

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.


5

Bạn nói gì? Thế còn #concatphươ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à <<.


7
Có một cách khác để gắn các chuỗi lại với nhau mà không được đề cập bởi những người khác, và đó chỉ bằng cách đặt cạnh nhau:"foo" "bar" 'baz" #=> "foobarabaz"
Boris Stitnicky

Lưu ý cho người khác: Đó không phải là một trích dẫn, mà là một trích dẫn giống như phần còn lại. Phương pháp gọn gàng!
Joshua Pinter

5

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ế ...


2

Bạn cũng có thể sử dụng %như sau:

source = "#{ROOT_DIR}/%s/App.config" % project

Cách tiếp cận này cũng hoạt động với 'dấu ngoặc kép (đơn).


2

Bạn có thể sử dụng +hoặc <<toán tử, nhưng trong .concatchứ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"))

Tôi nghĩ bạn có thêm .sau lần cuối concatkhông?
lacostenycoder

1

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.


1

Bạn có thể nối trực tiếp trong định nghĩa chuỗi:

nombre_apellido = "#{customer['first_name']} #{customer['last_name']} #{order_id}"

0

Đối với trường hợp cụ thể của bạn, bạn cũng có thể sử dụng Array#joinkhi 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"

0

Đối với con rối:

$username = 'lala'
notify { "Hello ${username.capitalize}":
    withpath => false,
}
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.