Rubyの文字列連結


364

Rubyで文字列を連結するよりエレガントな方法を探しています。

次の行があります。

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

これを行うより良い方法はありますか?

そして、そのことについては何の違いである<<とは+


3
この質問stackoverflow.com/questions/4684446/…は関連性が高いです。
アイ・

<<これは連結を行うためのより効率的な方法です。
Taimoor Changaiz 2017

回答:


574

あなたはそれをいくつかの方法で行うことができます:

  1. あなたが示したように<<、それは通常の方法ではありません
  2. 文字列補間あり

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

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

2番目の方法は、私が見たもの(ただし測定されていません)から、メモリ/速度の点でより効率的です。ROOT_DIRがnilの場合、3つのメソッドすべてが初期化されていない定数エラーをスローします。

パス名を扱うときはFile.join、パス名のセパレーターをめちゃくちゃにしないように使用したい場合があります。

結局、それは好みの問題です。


7
ルビーの経験はあまりありません。しかし、一般的には、多くの文字列を連結する場合、文字列を配列に追加し、最後に文字列をアトミックにまとめることでパフォーマンスを向上できることがよくあります。次に、<<は役に立ちますか?
PEZ

1
とにかく、メモリを追加し、長い文字列をそれにコピーする必要があります。<<は、1文字で<<できることを除いて、ほぼ+と同じです。
Keltia、2008

9
配列の要素で<<を使用する代わりに、Array#joinを使用すると、はるかに高速になります。
Grant Hutchins、

94

+オペレータは、通常の連結上の選択であり、そしておそらくCONCATENATE文字列への最速の方法です。

+とは、<<つまり<<、その左側のオブジェクトを変更し、そして+ありません。

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
+演算子は、文字列を連結する最速の方法ではありません。使用するたびにコピーが作成されますが、<<は適切に連結され、パフォーマンスが大幅に向上します。
Evil Trout

5
ほとんどの用途、補間のために、+<<ほぼ同じであることを行っています。多くの文字列、または本当に大きな文字列を扱っている場合、違いに気付くかもしれません。彼らのパフォーマンスがどれほど似ているかに驚いた。gist.github.com/2895311
Matt Burke

8
jrubyの結果は、早期実行のJVMオーバーロードによる補間に対して歪んでいます。5.times do ... endインタープリターごとにテストスイートを数回(同じプロセスで-つまり、すべてをブロックでラップして)実行すると、より正確な結果が得られます。私のテストでは、補間がすべてのRubyインタープリターの中で最も高速な方法であることが示されています。私は<<最速であると期待していましたが、それが私たちがベンチマークする理由です。
ウォンブル

Rubyに精通しているわけではないので、突然変異がスタックで実行されるのか、ヒープで実行されるのか気になりますか?ヒープ上にある場合、より高速であるように思われるミューテーション操作でさえ、おそらく何らかの形のmallocを必要とします。これがないと、バッファオーバーフローが発生するはずです。スタックを使用するとかなり高速になる可能性がありますが、結果の値はおそらくヒープに配置されるため、malloc操作が必要になります。最後に、変数参照によってインプレースミューテーションのように見えても、メモリポインターは新しいアドレスであると期待します。それで、本当に、違いはありますか?
ロビン・コー

79

パスを連結するだけの場合は、Ruby独自のFile.joinメソッドを使用できます。

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

これはこの要旨に触発された別のベンチマークです。動的文字列と事前定義文字列の連結(+)、追加(<<)、補間(#{})を比較します。

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)

結論:MRIの補間は重いです。


文字列は不変になり始めているので、このための新しいベンチマークを見たいです。
bibstha

7

パス名を使用したい:

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

程度<<+ルビーのドキュメントから:

+:strに連結されたother_strを含む新しい文字列を返します

<<:指定されたオブジェクトをstrに連結します。オブジェクトが0〜255のFixnumの場合、連結する前に文字に変換されます。

違いは、最初のオペランドになったものの中にあるので、(<<場所の変更、なります+(最初のオペランドがFixnumかの場合になりますし、何を返す新しい文字列それはメモリが重いように)<<、それはコードの文字は、その数に等しかったかのように追加され、+発生しますエラー)


2
argが絶対パスの場合、レシーバーパスが無視されるため、パス名で '+'を呼び出すと危険な場合があることを発見しましたPathname('/home/foo') + '/etc/passwd' # => #<Pathname:/etc/passwd>。これは、rubydocの例に基づいた仕様です。File.joinの方が安全であるようです。
ケルビン

また(Pathname(ROOT_DIR) + project + 'App.config').to_s、文字列オブジェクトを返したい場合にも呼び出す必要があります。
lacostenycoder

6

それについての私の経験をすべてお見せしましょう。

32kのレコードを返すクエリがありました。レコードごとに、データベースレコードをフォーマットされた文字列にフォーマットするメソッドを呼び出し、それを文字列に連結して、このすべてのプロセスの最後にディスク内のファイルに変換します。

私の問題は、記録によると、文字列を連結するプロセスが24k前後になるということでした。

私は通常の「+」演算子を使用してそれを行っていました。

私が「<<」に変わったとき、まるで魔法のようでした。本当に速かった。

それで、私はJavaを使用していて '+'を使用して文字列を連結し、StringからStringBufferに変更した(そして今、Java開発者はStringBuilderを使用している)昔のことを思い出しました。

Rubyの世界での+ / <<のプロセスは、Javaの世界での+ / StringBuilder.appendと同じだと思います。

最初はメモリ内のオブジェクト全体を再割り当てし、もう1つは新しいアドレスを指すだけです。


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"
ボリスStitnicky

他の人への注意:これは一重引用符ではなく、他の二重引用符と同じです。きちんとした方法!
ジョシュアピンター


2

%次のように使用することもできます。

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

このアプローチは、'(単一の)引用符でも機能します。


2

+or <<演算子を使用することもできますが、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オブジェクトは更新されませんが、2番目の例では、<<演算子は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,
}
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.