ฉันกำลังมองหาวิธีเชื่อมสตริงที่หรูหรากว่าใน Ruby
ฉันมีบรรทัดต่อไปนี้:
source = "#{ROOT_DIR}/" << project << "/App.config"
มีวิธีที่ดีกว่าในการทำเช่นนี้?
และสำหรับเรื่องนั้นอะไรคือความแตกต่างระหว่าง<<
และ+
?
ฉันกำลังมองหาวิธีเชื่อมสตริงที่หรูหรากว่าใน Ruby
ฉันมีบรรทัดต่อไปนี้:
source = "#{ROOT_DIR}/" << project << "/App.config"
มีวิธีที่ดีกว่าในการทำเช่นนี้?
และสำหรับเรื่องนั้นอะไรคือความแตกต่างระหว่าง<<
และ+
?
คำตอบ:
คุณสามารถทำได้หลายวิธี:
<<
แต่นั่นไม่ใช่วิธีปกติด้วยการแก้ไขสตริง
source = "#{ROOT_DIR}/#{project}/App.config"
กับ +
source = "#{ROOT_DIR}/" + project + "/App.config"
วิธีที่สองดูเหมือนจะมีประสิทธิภาพมากขึ้นในแง่ของหน่วยความจำ / ความเร็วจากสิ่งที่ฉันเห็น (ไม่ได้วัดว่า) ทั้งสามวิธีจะส่งข้อผิดพลาดคงที่ที่ไม่กำหนดค่าเริ่มต้นเมื่อ ROOT_DIR เป็นศูนย์
เมื่อจัดการกับชื่อพา ธ คุณอาจต้องการใช้ File.join
เพื่อหลีกเลี่ยงการสับสนกับตัวคั่นชื่อพา ธ
ในที่สุดมันเป็นเรื่องของรสนิยม
+
ดำเนินการคือตัวเลือกการต่อข้อมูลแบบปกติและอาจเป็นวิธีที่เร็วที่สุดในการต่อสตริง
ความแตกต่างระหว่าง+
และ<<
คือ<<
การเปลี่ยนวัตถุทางด้านซ้ายมือและ+
ไม่เปลี่ยนแปลง
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"
+
และ<<
จะใกล้เคียงกัน หากคุณกำลังจัดการกับสายอักขระจำนวนมากหรือสายใหญ่จริง ๆ คุณอาจสังเกตเห็นความแตกต่าง ฉันรู้สึกประหลาดใจกับการแสดงที่คล้ายกัน gist.github.com/2895311
5.times do ... end
บล็อก) สำหรับล่ามแต่ละตัวคุณจะได้ผลลัพธ์ที่แม่นยำยิ่งขึ้น การทดสอบของฉันแสดงให้เห็นถึงการแก้ไขเป็นวิธีที่เร็วที่สุดสำหรับล่าม Ruby ทั้งหมด ฉันคาดว่า<<
จะเร็วที่สุด แต่นั่นเป็นสาเหตุที่เราใช้เป็นมาตรฐาน
หากคุณเป็นเพียงการต่อพา ธ คุณสามารถใช้วิธี File.join ของรูบี้ได้
source = File.join(ROOT_DIR, project, 'App.config')
จาก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)
เนื่องจากนี่เป็นเส้นทางที่ฉันอาจใช้อาร์เรย์และเข้าร่วม:
source = [ROOT_DIR, project, 'App.config'] * '/'
นี่คือมาตรฐานอื่นที่ได้รับแรงบันดาลใจจากกระทู้นี้ มันเปรียบเทียบการต่อข้อมูล ( +
) ต่อท้าย ( <<
) และการแก้ไข ( #{}
) สำหรับสตริงแบบไดนามิกและที่กำหนดไว้ล่วงหน้า
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 นั้นหนัก
ฉันต้องการใช้ชื่อพา ธ :
require 'pathname' # pathname is in stdlib
Pathname(ROOT_DIR) + project + 'App.config'
เกี่ยวกับ<<
และ+
จากทับทิมเอกสาร:
+
: ส่งคืนสตริงใหม่ที่มี other_str ตัดแบ่งเป็น str
<<
: เชื่อมต่อวัตถุที่กำหนดเข้ากับ str หากวัตถุนั้นเป็น Fixnum ระหว่าง 0 ถึง 255 วัตถุนั้นจะถูกแปลงเป็นอักขระก่อนทำการต่อข้อมูล
ดังนั้นความแตกต่างในสิ่งที่จะกลายเป็นตัวถูกดำเนินการครั้งแรก ( <<
ทำการเปลี่ยนแปลงในสถานที่+
ส่งกลับสตริงใหม่ดังนั้นมันเป็นหน่วยความจำที่หนักกว่า) และสิ่งที่จะเป็นถ้าตัวถูกดำเนินการแรกคือ Fixnum ( <<
จะเพิ่มราวกับว่ามันเป็นตัวละครที่มีรหัสเท่ากับจำนวนนั้น+
จะเพิ่ม ข้อผิดพลาด)
Pathname('/home/foo') + '/etc/passwd' # => #<Pathname:/etc/passwd>
อาจเป็นอันตรายได้เพราะถ้าหาเรื่องเป็นเส้นทางแน่นอนเส้นทางรับจะถูกละเว้น: นี่คือการออกแบบตามตัวอย่าง rubydoc ดูเหมือนว่า File.join จะปลอดภัยกว่า
(Pathname(ROOT_DIR) + project + 'App.config').to_s
ถ้าคุณต้องการกลับวัตถุสตริง
ให้ฉันแสดงให้คุณเห็นประสบการณ์ทั้งหมดของฉันกับที่
ฉันมีคิวรีที่ส่งคืนเร็กคอร์ด 32k สำหรับแต่ละเร็กคอร์ดฉันเรียกวิธีการจัดรูปแบบเร็กคอร์ดฐานข้อมูลนั้นเป็นสตริงที่จัดรูปแบบและต่อกับที่เป็นสตริงที่ส่วนท้ายของกระบวนการนี้ทั้งหมดจะเปลี่ยนเป็นไฟล์ในดิสก์
ปัญหาของฉันคือโดยการบันทึกไปประมาณ 24k กระบวนการของการเชื่อมต่อสตริงเปิดความเจ็บปวด
ฉันทำเช่นนั้นโดยใช้ตัวดำเนินการ '+' ปกติ
เมื่อฉันเปลี่ยนเป็น '<<' ก็เหมือนเวทมนต์ มันเร็วจริงๆ
ดังนั้นฉันจำได้ว่าครั้งเก่าของฉัน - เรียงลำดับจากปี 1998 - เมื่อฉันใช้ Java และเชื่อมสตริงโดยใช้ '+' และเปลี่ยนจาก String เป็น StringBuffer (และตอนนี้เราผู้พัฒนา Java มี StringBuilder)
ฉันเชื่อว่ากระบวนการของ / / << ใน Ruby world เหมือนกับ + / StringBuilder.append ในโลก Java
ครั้งแรกที่จัดสรรวัตถุทั้งหมดในหน่วยความจำและอื่น ๆ เพียงแค่ชี้ไปที่ที่อยู่ใหม่
คุณพูดต่อกันไหม? วิธีการเกี่ยวกับ#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
<<
"foo" "bar" 'baz" #=> "foobarabaz"
นี่คือวิธีเพิ่มเติมในการทำสิ่งนี้:
"String1" + "String2"
"#{String1} #{String2}"
String1<<String2
และอื่น ๆ ...
คุณยังสามารถใช้%
ดังต่อไปนี้:
source = "#{ROOT_DIR}/%s/App.config" % project
วิธีนี้ใช้ได้กับ'
เครื่องหมายคำพูด (เดี่ยว) เช่นกัน
คุณอาจจะใช้+
หรือ<<
โอเปอเรเตอร์ แต่ใน.concat
ฟังก์ชั่นทับทิมเป็นสิ่งที่ดีที่สุดเพราะมันเร็วกว่าโอเปอเรเตอร์อื่น ๆ คุณสามารถใช้มันเหมือน
source = "#{ROOT_DIR}/".concat(project.concat("/App.config"))
.
หลังจากที่concat
ไม่ได้?
สถานการณ์มีความสำคัญเช่น:
# 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
วัตถุด้วยการวนซ้ำแต่ละครั้ง ดังนั้นสำหรับสถานการณ์ประเภทข้างต้น<<
จะดีกว่า
คุณสามารถต่อข้อมูลสตริงเข้าด้วยกันโดยตรง:
nombre_apellido = "#{customer['first_name']} #{customer['last_name']} #{order_id}"
สำหรับกรณีเฉพาะของคุณคุณสามารถใช้Array#join
เมื่อสร้างประเภทเส้นทางของสตริง:
string = [ROOT_DIR, project, 'App.config'].join('/')]
สิ่งนี้มีผลข้างเคียงที่น่าพึงพอใจจากการแปลงสตริงชนิดต่าง ๆ โดยอัตโนมัติ:
['foo', :bar, 1].join('/')
=>"foo/bar/1"
สำหรับหุ่นเชิด:
$username = 'lala'
notify { "Hello ${username.capitalize}":
withpath => false,
}