З'єднання струн у Рубі


364

Я шукаю більш елегантний спосіб об'єднання струн у Ruby.

У мене є такий рядок:

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

Чи є приємніший спосіб зробити це?

І з цього питання в чому різниця між <<і +?


3
Це питання stackoverflow.com/questions/4684446/… дуже пов'язане.
Очі

<< це більш ефективний спосіб зробити конкатенацію.
Taimoor Chagaiz

Відповіді:


574

Це можна зробити кількома способами:

  1. Як ви показали, <<але це не звичайний спосіб
  2. За допомогою рядкової інтерполяції

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

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

Другий метод, здається, є більш ефективним щодо пам’яті / швидкості, ніж я бачив (хоча не вимірюється). Усі три методи видадуть неініціалізовану постійну помилку, коли ROOT_DIR дорівнює нулю.

Маючи справу з іменами шляхів, можливо, ви хочете використовувати, File.joinщоб уникнути псування з роздільником імен шляхів.

Зрештою, це питання смаку.


7
Я не дуже досвідчений з рубіном. Але, як правило, у випадках, коли ви об'єднуєте безліч рядків, ви часто можете отримувати продуктивність, додаючи рядки до масиву, а потім в кінці з'єднуйте рядок атомним шляхом. Тоді << може бути корисним?
ПЕЗ

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
Результати вашої дружини перекошені від інтерполяції через раннє перевантаження JVM. Якщо ви запустите тестовий набір кілька разів (в одному і тому ж процесі - тому загортайте все в скажімо 5.times do ... endблок) для кожного перекладача, ви отримаєте більш точні результати. Моє тестування показало, що інтерполяція є найшвидшим методом у всіх перекладачів Ruby. Я б очікував <<бути найшвидшим, але саме тому ми орієнтуємося.
жіночий

Не надто розбираючись у Рубі, мені цікаво, чи мутація виконується на стеці чи купі? Якщо на купі, навіть мутація, яка, здається, повинна бути швидшою, ймовірно, передбачає певну форму лука. Без цього я очікую переповнення буфера. Використання стека може бути досить швидким, але отримане значення, ймовірно, все-таки розміщується на купі, що вимагає операції з malloc. Зрештою, я очікую, що вказівник пам'яті стане новою адресою, навіть якщо змінна посилання робить його схожим на мутацію на місці. Отже, справді, чи є різниця?
Робін Коу

79

Якщо ви просто об'єднуєте шляхи, ви можете використовувати власний метод File.join Ruby.

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

5
Це, мабуть, шлях, оскільки рубін піклується про створення правильної рядка в системі з різними роздільниками шляху.
ПЕЗ

26

від http://greyblake.com/blog/2012/09/02/ruby-perfomance-tricks/

Використання <<ака 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

Ось ще один орієнтир, натхненний цією суттю . Він порівнює конкатенацію ( +), додавання ( <<) та інтерполяцію ( #{}) для динамічних та заздалегідь заданих рядків.

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

<<: Об'єднає даний об'єкт з str. Якщо об'єктом є Fixnum між 0 і 255, він перетворюється в символ перед конкатенацією.

тому різниця полягає в тому, що стає першим операндом ( <<вносить зміни на місце, +повертає нову рядок, щоб вона була важче пам’яті) і що буде, якщо перший операнд буде Fixnum ( <<додасть, ніби він був символом з кодом, рівним цьому номеру, +підніме помилка)


2
Я тільки що виявив , що виклик «+» на Pathname може бути небезпечно , тому що , якщо аргумент є абсолютним шляхом, шлях приймача ігнорується: Pathname('/home/foo') + '/etc/passwd' # => #<Pathname:/etc/passwd>. Це за дизайном, заснованим на прикладі рубідока. Здається, що File.join безпечніший.
Келвін

також вам потрібно зателефонувати, (Pathname(ROOT_DIR) + project + 'App.config').to_sякщо ви хочете повернути рядковий об'єкт.
lacostenycoder

6

Дозвольте показати вам увесь мій досвід з цим.

У мене був запит, який повертав 32 к записів, для кожного запису я закликав метод відформатувати цю базу даних у форматовану рядок і потім об'єднати її в String, який в кінці всього цього процесу перетвориться на файл на диску.

Моя проблема полягала в тому, що, за записом, близько 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

Ви можете використовувати +або <<оператора, але .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.