루비에서 문자열 연결


364

Ruby에서 문자열을 연결하는보다 우아한 방법을 찾고 있습니다.

다음 줄이 있습니다.

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

더 좋은 방법이 있습니까?

그리고 그 문제의 차이 무엇 <<+?


3
이 질문은 stackoverflow.com/questions/4684446/… 와 관련이 있습니다.

<< 연결을 수행하는보다 효율적인 방법입니다.
Taimoor Changaiz '12

답변:


574

여러 가지 방법으로이를 수행 할 수 있습니다.

  1. 당신이 보여 주 <<었지만 그것은 일반적인 방법 이 아닙니다
  2. 문자열 보간

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

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

두 번째 방법은 내가 본 것보다 메모리 / 속도면에서 더 효율적인 것으로 보입니다 (측정하지는 않음). ROOT_DIR이 nil이면 세 가지 방법 모두 초기화되지 않은 상수 오류를 발생시킵니다.

경로 이름을 다룰 때 File.join경로 이름 구분 기호가 엉망이되는 것을 피하기 위해 사용할 수 있습니다 .

결국, 그것은 맛의 문제입니다.


7
나는 루비에 익숙하지 않습니다. 그러나 일반적으로 많은 문자열을 연결하는 경우 문자열을 배열에 추가하여 마지막으로 문자열을 원자 적으로 결합하여 성능을 얻을 수 있습니다. 그렇다면 <<가 유용 할 수 있습니까?
PEZ

1
어쨌든 더 긴 문자열을 메모리에 추가해야합니다. <<는 단일 문자로 <<를 할 수 있다는 점을 제외하면 +와 거의 동일합니다.
Keltia

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
초기 실행 된 JVM 과부하로 인해 jruby 결과가 보간에 대해 왜곡됩니다. 5.times do ... end각 인터프리터에 대해 테스트 스위트를 여러 번 실행하면 (동일한 프로세스에서 모든 것을 블록으로 감싸서 ) 더 정확한 결과를 얻을 수 있습니다. 내 테스트에서 보간이 모든 Ruby 인터프리터에서 가장 빠른 방법임을 보여주었습니다. <<가장 빠를 것으로 예상 했지만 벤치마킹하는 이유입니다.
울림

Ruby에 너무 정통하지는 않지만 스택 또는 힙에서 돌연변이가 수행되는지 궁금합니다. 힙에 있다면, 더 빠른 것처럼 보이는 돌연변이 작업조차도 아마도 어떤 형태의 malloc을 포함 할 것입니다. 그것이 없으면 버퍼 오버플로가 예상됩니다. 스택 사용은 매우 빠를 수 있지만 결과 값은 어쨌든 힙에 배치되어 malloc 작업이 필요합니다. 결국, 변수 참조가 내부 돌연변이처럼 보이더라도 메모리 포인터가 새로운 주소가 될 것으로 기대합니다. 정말 차이가 있습니까?
Robin Coe

79

경로를 연결하는 경우 Ruby 고유의 File.join 메소드를 사용할 수 있습니다.

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

5
루비가 다른 경로 구분 기호를 사용하여 시스템에서 올바른 문자열을 만드는 것을 처리 한 이후로 갈 길입니다.
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의 보간법은 무겁습니다.


문자열은 이제 불변이기 시작하므로 새로운 벤치 마크를보고 싶습니다.
빕사

7

Pathname을 사용하고 싶습니다.

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

<<+루비 문서에서 :

+: str에 연결된 other_str을 포함 하는 새로운 문자열을 반환

<<: 주어진 객체를 str에 연결합니다. 오브젝트가 0-255 사이의 Fixnum 인 경우, 연결하기 전에 문자로 변환됩니다.

차이는 첫 번째 피연산자로 될 것입니다 때문에 ( <<장소 변경합니다 +(첫 번째 피연산자가 Fixnum이라는 경우가 될 것입니다 무엇을 반환 새 문자열이 메모리가 무거운 정도) <<가 코드 문자가 그 수만큼 인 것처럼 추가합니다, +올릴 것이다 오류)


2
arg가 절대 경로이면 수신자 경로가 무시되기 때문에 Pathname에서 '+'를 호출하는 것이 위험 할 수 있음을 방금 발견했습니다 Pathname('/home/foo') + '/etc/passwd' # => #<Pathname:/etc/passwd>. 이것은 루비 독 예제를 기반으로 의도적으로 설계된 것입니다. File.join이 더 안전한 것 같습니다.
Kelvin

또한 (Pathname(ROOT_DIR) + project + 'App.config').to_s문자열 객체를 반환 하려면 호출해야 합니다.
lacostenycoder

6

그것에 대한 모든 경험을 보여 드리겠습니다.

32k의 레코드를 반환하는 쿼리가있었습니다. 각 레코드마다 데이터베이스 레코드를 형식화 된 문자열로 형식화하는 방법을 호출 하고이 모든 프로세스가 끝나면 디스크의 파일로 변환되는 문자열로 연결합니다.

내 문제는 레코드가 24k 전후에 문자열을 연결하는 프로세스가 고통을 겪었다는 것입니다.

나는 정규 '+'연산자를 사용하여 그렇게하고있었습니다.

'<<'로 바꾸었을 때 마술 같았습니다. 정말 빠르다.

그래서 Java를 사용하고 '+'를 사용하여 String을 연결하고 String에서 StringBuffer로 변경했을 때 (1998 년 일종의) 옛날을 기억했습니다 (이제 Java 개발자에게는 StringBuilder가 있습니다).

Ruby 세계에서 + / <<의 프로세스는 Java 세계에서 + / StringBuilder.append와 동일하다고 생각합니다.

첫 번째는 전체 객체를 메모리에 재 할당하고 다른 하나는 새 주소를 가리 킵니다.


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"
붙일 수있는

다른 사람들에게 참고 : 그것은 작은 따옴표가 아니라 나머지와 같은 이중 따옴표이어야합니다. 깔끔한 방법!
Joshua Pinter

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

첫 번째 예에서 +operator 와 연결 하면 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,
}
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.