Конкатенация строк в Ruby


364

Я ищу более элегантный способ объединения строк в Ruby.

У меня есть следующая строка:

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

Есть ли лучший способ сделать это?

И в этом отношении, в чем разница между <<и +?


3
Этот вопрос stackoverflow.com/questions/4684446/… тесно связан.
— Eye

<< это более эффективный способ объединения.
— Таймур Чангайз

Ответы:


575

Вы можете сделать это несколькими способами:

  1. Как вы показали, <<но это не обычный способ
  2. Со строковой интерполяцией

    source = "#{ROOT_DIR}/#{project}/App.config"
  3. с +

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

Второй метод кажется более эффективным с точки зрения памяти / скорости из того, что я видел (хотя и не измерялось). Все три метода выдают неинициализированную постоянную ошибку, когда ROOT_DIR равен nil.

При работе с путевыми именами вы можете использовать их, File.joinчтобы избежать путаницы с разделителем путевых имен.

В конце концов, это вопрос вкуса.


7
Я не очень опытен с рубином. Но, как правило, в случаях, когда вы объединяете множество строк, вы часто можете повысить производительность, добавляя строки в массив, а затем в конце соединяйте строку атомарно. Тогда << может быть полезным?
— PEZ

1
Вам все равно придется добавить в память копию более длинной строки. << более или менее совпадает с +, за исключением того, что вы можете << с одним символом.
— Келтия

9
Вместо использования << в элементах массива, используйте Array # join, это намного быстрее.
— Грант Хатчинс

94

+Оператор нормальный выбор конкатенации, и, вероятно , самый быстрый способ конкатенации строк.

Разница между +и <<заключается в том, <<что объект изменяется с левой стороны, а +не изменяется .

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
Оператор + определенно не самый быстрый способ объединения строк. Каждый раз, когда вы используете его, он делает копию, тогда как << сцепляется на месте и является гораздо более производительным.
— Злая форель

5
Для большинства применений интерполяция +и <<будет примерно одинаковой. Если вы имеете дело со многими или очень большими строками, то вы можете заметить разницу. Я был удивлен тем, насколько похожи они выступили. gist.github.com/2895311
— Мэтт Бёрк,

8
Ваши результаты jruby искажаются от интерполяции из-за ранней перегрузки JVM. Если вы запустите набор тестов несколько раз (в одном и том же процессе - так что оберните все, скажем, в 5.times do ... endблок) для каждого интерпретатора, вы получите более точные результаты. Мое тестирование показало, что интерполяция - самый быстрый метод среди всех интерпретаторов Ruby. Я ожидал <<бы быть самым быстрым, но именно поэтому мы тестируем.
— Уомбл

Не слишком разбираясь в Ruby, мне интересно, выполняется ли мутация в стеке или в куче? Если в куче, даже операция мутации, которая, кажется, должна быть быстрее, вероятно, включает в себя некоторую форму malloc. Без этого я бы ожидал переполнение буфера. Использование стека может быть довольно быстрым, но результирующее значение, вероятно, все равно помещается в кучу, что требует операции malloc. В конце я ожидаю, что указатель памяти будет новым адресом, даже если ссылка на переменную делает его похожим на мутацию на месте. Так есть ли разница?
— Робин Коу

79

Если вы просто объединяете пути, вы можете использовать собственный метод File.join в Ruby.

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

5
Похоже, что так будет лучше, так как тогда ruby ​​позаботится о создании правильной строки в системе с разными разделителями пути.
— PEZ

26

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

Использование <<aka concatнамного эффективнее +=, поскольку последний создает временный объект и переопределяет первый объект новым объектом.

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

вывод:

____________________________________________________________
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

Поскольку это путь, я бы, вероятно, использовал массив и соединение:

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

9

Вот еще один ориентир, вдохновленный этой сущностью . Он сравнивает concatenation ( +), appending ( <<) и interpolation ( #{}) для динамических и предопределенных строк.

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

вывод:

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)

Вывод: интерполяция в МРТ тяжелая.


Поскольку строки становятся неизменяемыми, я бы хотел увидеть новый тест для этого.
— bibstha

7

Я бы предпочел использовать Pathname:

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

о <<и +из рубиновых документов:

+: Возвращает новую строку, содержащую other_str, соединенную с str

<<: Объединяет данный объект на ул. Если объект является Fixnum между 0 и 255, он преобразуется в символ перед конкатенацией.

так что разница в том, что становится первым операндом ( <<вносит изменения на месте, +возвращает новую строку, так что она становится тяжелее в памяти), и то, что будет, если первый операнд будет Fixnum ( <<будет добавлено, как если бы это был символ с кодом, равным этому числу, +повысит ошибка)


2
Я только что обнаружил , что вызов «+» на Pathname может быть опасно , потому что , если аргумент является абсолютным путем, путь приемника игнорируется: Pathname('/home/foo') + '/etc/passwd' # => #<Pathname:/etc/passwd>. Это разработано на основе примера Rubydoc. Кажется, что File.join безопаснее.
— Кельвин

Также вам нужно позвонить, (Pathname(ROOT_DIR) + project + 'App.config').to_sесли вы хотите вернуть строковый объект.
— lacostenycoder

6

Позвольте мне показать вам весь мой опыт с этим.

У меня был запрос, который возвращал 32 тыс. Записей, для каждой записи я вызывал метод для форматирования этой записи базы данных в форматированную строку и затем объединял ее в строку, которая в конце всего этого процесса превращается в файл на диске.

Моя проблема заключалась в том, что, согласно записи, около 24 тыс., Процесс конкатенации строки включал боль.

Я делал это, используя обычный оператор «+».

Когда я перешел на «<<», это было похоже на магию. Было действительно быстро.

Итак, я вспомнил свои старые времена, вроде 1998 года, когда я использовал Java и конкатенировал String с помощью '+' и изменил с String на StringBuffer (и теперь у нас, разработчика Java, есть StringBuilder).

Я считаю, что процесс + / << в мире Ruby такой же, как + / StringBuilder.append в мире Java.

Первые перераспределяют весь объект в памяти, а другие просто указывают на новый адрес.


5

Сцепление говорите? Как насчет #concatметода тогда?

a = 'foo'
a.object_id #=> some number
a.concat 'bar' #=> foobar
a.object_id #=> same as before -- string a remains the same object

Справедливости ради, concatпсевдоним как <<.


7
Есть еще один способ склеивания строк, не упомянутый другими, и это просто путем сопоставления:"foo" "bar" 'baz" #=> "foobarabaz"
— Борис Ститницкий,

Примечание для других: это должна быть не одинарная кавычка, а двойная, как и остальные. Аккуратный метод!
— Джошуа Пинтер

5

Вот еще несколько способов сделать это:

"String1" + "String2"

"#{String1} #{String2}"

String1<<String2

И так далее ...


2

Вы также можете использовать %следующее:

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

Этот подход также работает с '(одинарной) кавычкой.


2

Вы можете использовать оператор +или <<оператор, но в Ruby .concatфункция является наиболее предпочтительной, поскольку она намного быстрее, чем другие операторы. Вы можете использовать это как.

source = "#{ROOT_DIR}/".concat(project.concat("/App.config"))

Я думаю, у вас есть лишнее .после вашего последнего concatнет?
— lacostenycoder

1

Ситуация имеет значение, например:

# 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

В первом примере объединение с +оператором не будет обновлять outputобъект, однако во втором примере <<оператор будет обновлять outputобъект с каждой итерацией. Таким образом, для ситуации вышеупомянутого типа, <<лучше.


1

Вы можете объединить в определении строки напрямую:

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

0

Для вашего конкретного случая вы также можете использовать Array#joinпри построении пути к файлу тип строки:

string = [ROOT_DIR, project, 'App.config'].join('/')]

Это имеет приятный побочный эффект автоматического преобразования различных типов в строку:

['foo', :bar, 1].join('/')
=>"foo/bar/1"

0

Для кукол:

$username = 'lala'
notify { "Hello ${username.capitalize}":
    withpath => false,
}
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.