การต่อสตริงใน Ruby


364

ฉันกำลังมองหาวิธีเชื่อมสตริงที่หรูหรากว่าใน Ruby

ฉันมีบรรทัดต่อไปนี้:

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

มีวิธีที่ดีกว่าในการทำเช่นนี้?

และสำหรับเรื่องนั้นอะไรคือความแตกต่างระหว่าง<<และ+?


3
คำถามนี้stackoverflow.com/questions/4684446/…มีความเกี่ยวข้องสูง
ตา

<< นี่เป็นวิธีที่มีประสิทธิภาพมากขึ้นในการต่อกัน
Taimoor Changaiz

คำตอบ:


574

คุณสามารถทำได้หลายวิธี:

  1. ตามที่คุณแสดงด้วย<<แต่นั่นไม่ใช่วิธีปกติ
  2. ด้วยการแก้ไขสตริง

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

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

วิธีที่สองดูเหมือนจะมีประสิทธิภาพมากขึ้นในแง่ของหน่วยความจำ / ความเร็วจากสิ่งที่ฉันเห็น (ไม่ได้วัดว่า) ทั้งสามวิธีจะส่งข้อผิดพลาดคงที่ที่ไม่กำหนดค่าเริ่มต้นเมื่อ ROOT_DIR เป็นศูนย์

เมื่อจัดการกับชื่อพา ธ คุณอาจต้องการใช้ File.joinเพื่อหลีกเลี่ยงการสับสนกับตัวคั่นชื่อพา ธ

ในที่สุดมันเป็นเรื่องของรสนิยม


7
ฉันไม่ค่อยมีประสบการณ์กับทับทิม แต่โดยทั่วไปในกรณีที่คุณเชื่อมสตริงจำนวนมากคุณมักจะได้รับประสิทธิภาพโดยการผนวกสตริงเข้ากับอาร์เรย์จากนั้นในตอนท้ายให้ใส่สตริงเข้าด้วยกันแบบอะตอม ถ้าเช่นนั้น << จะมีประโยชน์ไหม?
PEZ

1
คุณจะต้องเพิ่มหน่วยความจำและคัดลอกสตริงที่มีความยาวเข้าไป << มีมากขึ้นหรือน้อยลงเช่นเดียวกับ + ยกเว้นว่าคุณสามารถ << ด้วยอักขระเดียว
Keltia

9
แทนที่จะใช้ << ในองค์ประกอบของอาร์เรย์ให้ใช้ Array # join มันเร็วกว่ามาก
Grant Hutchins

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
ตัวดำเนินการ + ไม่ใช่วิธีที่เร็วที่สุดในการต่อสตริงเข้าด้วยกัน ทุกครั้งที่คุณใช้มันจะทำสำเนาในขณะที่ << เชื่อมเข้าด้วยกันและมีประสิทธิภาพมากกว่า
Evil Trout

5
สำหรับการใช้งานส่วนใหญ่การแก้ไข+และ<<จะใกล้เคียงกัน หากคุณกำลังจัดการกับสายอักขระจำนวนมากหรือสายใหญ่จริง ๆ คุณอาจสังเกตเห็นความแตกต่าง ฉันรู้สึกประหลาดใจกับการแสดงที่คล้ายกัน gist.github.com/2895311
Matt Burke

8
ผลลัพธ์ jruby ของคุณบิดเบือนการแก้ไขโดยการเรียกใช้ JVM มากเกินไป หากคุณเรียกใช้ชุดทดสอบหลายครั้ง (ในกระบวนการเดียวกันดังนั้นให้ห่อทุกอย่างเป็น5.times do ... endบล็อก) สำหรับล่ามแต่ละตัวคุณจะได้ผลลัพธ์ที่แม่นยำยิ่งขึ้น การทดสอบของฉันแสดงให้เห็นถึงการแก้ไขเป็นวิธีที่เร็วที่สุดสำหรับล่าม Ruby ทั้งหมด ฉันคาดว่า<<จะเร็วที่สุด แต่นั่นเป็นสาเหตุที่เราใช้เป็นมาตรฐาน
womble

ไม่ได้มีประสบการณ์กับ Ruby ฉันอยากรู้ว่าการผ่าเหล่าจะเกิดขึ้นในกองหรือกองหรือไม่ ถ้าอยู่บนฮีปแม้กระทั่งการผ่าเหล่าซึ่งดูเหมือนว่ามันจะเร็วกว่า ถ้าไม่มีมันฉันคาดหวังว่าบัฟเฟอร์ล้น การใช้สแต็กนั้นค่อนข้างเร็ว แต่ค่าผลลัพธ์อาจถูกวางไว้บนฮีปอย่างไรก็ตามต้องมีการดำเนินการ malloc ในที่สุดฉันคาดหวังว่าตัวชี้หน่วยความจำจะเป็นที่อยู่ใหม่แม้ว่าการอ้างอิงตัวแปรทำให้ดูเหมือนการกลายพันธุ์ในสถานที่ ดังนั้นจริงๆมีความแตกต่าง?
Robin Coe

79

หากคุณเป็นเพียงการต่อพา ธ คุณสามารถใช้วิธี 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 นั้นหนัก


เนื่องจากสตริงกำลังเริ่มเปลี่ยนแปลงไม่ได้ในตอนนี้ฉันชอบที่จะเห็นมาตรฐานใหม่สำหรับสิ่งนี้
bibstha

7

ฉันต้องการใช้ชื่อพา ธ :

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

เกี่ยวกับ<<และ+จากทับทิมเอกสาร:

+: ส่งคืนสตริงใหม่ที่มี other_str ตัดแบ่งเป็น str

<<: เชื่อมต่อวัตถุที่กำหนดเข้ากับ str หากวัตถุนั้นเป็น Fixnum ระหว่าง 0 ถึง 255 วัตถุนั้นจะถูกแปลงเป็นอักขระก่อนทำการต่อข้อมูล

ดังนั้นความแตกต่างในสิ่งที่จะกลายเป็นตัวถูกดำเนินการครั้งแรก ( <<ทำการเปลี่ยนแปลงในสถานที่+ส่งกลับสตริงใหม่ดังนั้นมันเป็นหน่วยความจำที่หนักกว่า) และสิ่งที่จะเป็นถ้าตัวถูกดำเนินการแรกคือ Fixnum ( <<จะเพิ่มราวกับว่ามันเป็นตัวละครที่มีรหัสเท่ากับจำนวนนั้น+จะเพิ่ม ข้อผิดพลาด)


2
ฉันเพิ่งค้นพบว่าการเรียก '+' ในชื่อพา ธ Pathname('/home/foo') + '/etc/passwd' # => #<Pathname:/etc/passwd>อาจเป็นอันตรายได้เพราะถ้าหาเรื่องเป็นเส้นทางแน่นอนเส้นทางรับจะถูกละเว้น: นี่คือการออกแบบตามตัวอย่าง rubydoc ดูเหมือนว่า File.join จะปลอดภัยกว่า
เคลวิน

นอกจากนี้คุณต้องโทร(Pathname(ROOT_DIR) + project + 'App.config').to_sถ้าคุณต้องการกลับวัตถุสตริง
lacostenycoder

6

ให้ฉันแสดงให้คุณเห็นประสบการณ์ทั้งหมดของฉันกับที่

ฉันมีคิวรีที่ส่งคืนเร็กคอร์ด 32k สำหรับแต่ละเร็กคอร์ดฉันเรียกวิธีการจัดรูปแบบเร็กคอร์ดฐานข้อมูลนั้นเป็นสตริงที่จัดรูปแบบและต่อกับที่เป็นสตริงที่ส่วนท้ายของกระบวนการนี้ทั้งหมดจะเปลี่ยนเป็นไฟล์ในดิสก์

ปัญหาของฉันคือโดยการบันทึกไปประมาณ 24k กระบวนการของการเชื่อมต่อสตริงเปิดความเจ็บปวด

ฉันทำเช่นนั้นโดยใช้ตัวดำเนินการ '+' ปกติ

เมื่อฉันเปลี่ยนเป็น '<<' ก็เหมือนเวทมนต์ มันเร็วจริงๆ

ดังนั้นฉันจำได้ว่าครั้งเก่าของฉัน - เรียงลำดับจากปี 1998 - เมื่อฉันใช้ Java และเชื่อมสตริงโดยใช้ '+' และเปลี่ยนจาก String เป็น StringBuffer (และตอนนี้เราผู้พัฒนา Java มี StringBuilder)

ฉันเชื่อว่ากระบวนการของ / / << ใน Ruby world เหมือนกับ + / 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"
Boris Stitnicky

หมายเหตุถึงคนอื่น ๆ : นั่นไม่ควรจะเป็นคำพูดเดียว แต่เป็นคำที่สองเหมือนที่เหลือ เรียบร้อยแล้ว!
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

ในตัวอย่างแรกการต่อข้อมูลกับ+ตัวดำเนินการจะไม่อัปเดต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"

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.