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

364

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

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

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

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

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

dagda1
источник
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чтобы избежать путаницы с разделителем путевых имен.

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

Keltia
источник
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')
Georg
источник
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}"
jmojico
источник
0

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

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

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

['foo', :bar, 1].join('/')
=>"foo/bar/1"
lacostenycoder
источник
0

Для кукол:

$username = 'lala'
notify { "Hello ${username.capitalize}":
    withpath => false,
}
qräbnö
источник