เปลี่ยนค่าทุกค่าในแฮชใน Ruby


170

ฉันต้องการเปลี่ยนค่าทุกค่าในแฮชเพื่อเพิ่ม '%' ก่อนและหลังค่า

{ :a=>'a' , :b=>'b' }

จะต้องเปลี่ยนเป็น

{ :a=>'%a%' , :b=>'%b%' }

วิธีที่ดีที่สุดในการทำเช่นนี้คืออะไร?


1
กรุณาชี้แจงหากคุณต้องการที่จะกลายพันธุ์วัตถุสตริงเดิมเพียงแค่กลายพันธุ์เดิมมีหรือกลายพันธุ์อะไร
Phrogz

1
ถ้าอย่างนั้นคุณก็ยอมรับคำตอบที่ผิด (ยังไม่มีการกระทำผิดกฎหมายที่จะ @pst ตั้งใจเป็นผมเองยังสนับสนุนการทำงานในรูปแบบการเขียนโปรแกรมแทนกรรมวิธีวัตถุ.)
Phrogz

แต่ก็ยังคงเป็นวิธีที่ดีซู
theReverseFlick

คำตอบ:


178

หากคุณต้องการให้สตริงที่แท้จริงกลายพันธุ์เอง (อาจเป็นไปได้และน่าปรารถนาที่จะส่งผลกระทบต่อการอ้างอิงอื่น ๆ ไปยังวัตถุสตริงเดียวกัน):

# Two ways to achieve the same result (any Ruby version)
my_hash.each{ |_,str| str.gsub! /^|$/, '%' }
my_hash.each{ |_,str| str.replace "%#{str}%" }

หากคุณต้องการให้แฮชเปลี่ยน แต่คุณไม่ต้องการส่งผลกระทบต่อสตริง (คุณต้องการให้แฮ็กได้รับสตริงใหม่):

# Two ways to achieve the same result (any Ruby version)
my_hash.each{ |key,str| my_hash[key] = "%#{str}%" }
my_hash.inject(my_hash){ |h,(k,str)| h[k]="%#{str}%"; h }

หากคุณต้องการแฮชใหม่:

# Ruby 1.8.6+
new_hash = Hash[*my_hash.map{|k,str| [k,"%#{str}%"] }.flatten]

# Ruby 1.8.7+
new_hash = Hash[my_hash.map{|k,str| [k,"%#{str}%"] } ]

1
@ Andrew Marshall ใช่แล้วคุณล่ะขอบคุณ ใน Ruby 1.8 Hash.[]ไม่ยอมรับอาร์เรย์ของคู่ของอาเรย์มันต้องมีการโต้แย้งโดยตรงจำนวนคู่
Phrogz

2
จริงๆแล้ว Hash. [key_value_pairs] ถูกนำมาใช้ใน 1.8.7 ดังนั้น Ruby 1.8.6 เท่านั้นจึงไม่จำเป็นต้องมีเครื่องหมายสีแดง
Marc-André Lafortune

2
@Aupajo Hash#eachให้ทั้งคีย์และค่าของบล็อก ในกรณีนี้ฉันไม่สนใจเกี่ยวกับกุญแจดังนั้นฉันจึงไม่ได้ตั้งชื่อสิ่งที่มีประโยชน์ ชื่อตัวแปรอาจเริ่มต้นด้วยการขีดเส้นใต้และอันที่จริงอาจเป็นเพียงการขีดเส้นใต้ ไม่มีประโยชน์ด้านประสิทธิภาพในการทำสิ่งนี้มันเป็นเพียงแค่การบันทึกเอกสารด้วยตนเองที่ละเอียดอ่อนว่าฉันไม่ได้ทำอะไรกับค่าบล็อกแรก
Phrogz

1
ฉันคิดว่าคุณหมายความว่าmy_hash.inject(my_hash){ |h,(k,str)| h[k]="%#{str}%"; h }ต้องคืนแฮชจาก block
aceofspades

1
อีกวิธีหนึ่งคุณอาจใช้วิธี each_value ซึ่งจะเข้าใจได้ง่ายกว่าการใช้เครื่องหมายขีดล่างสำหรับค่าคีย์ที่ไม่ได้ใช้
Strand McCutchen

266

ใน Ruby 2.1 ขึ้นไปคุณสามารถทำได้

{ a: 'a', b: 'b' }.map { |k, str| [k, "%#{str}%"] }.to_h

5
ขอบคุณจริง ๆ แล้วเป็นสิ่งที่ฉันกำลังมองหา ไม่รู้ว่าทำไมคุณถึงไม่ได้ลงคะแนนมากขนาดนั้น
simperreault

7
อย่างไรก็ตามนี่คือช้ามากและ RAM หิวมาก อินพุต Hash ซ้ำแล้วซ้ำอีกเพื่อสร้างชุดข้อมูลกลางที่ซ้อนกันซึ่งจะถูกแปลงเป็นแฮชใหม่ การเพิกเฉยต่อการใช้ RAM สูงสุดเวลารันไทม์ยิ่งแย่ลงมากการเทียบเคียงสิ่งนี้กับโซลูชันที่แก้ไขในคำตอบอื่นแสดง 2.5s กับ 1.5s ในจำนวนการทำซ้ำที่เท่ากัน ตั้งแต่ทับทิมเป็นภาษาช้าเมื่อเทียบกับการหลีกเลี่ยงบิตช้าของภาษาช้าทำให้ความรู้สึกมาก :-)
แอนดรูฮอดจ์กินสัน

2
@AndrewHodgkinson โดยทั่วไปฉันเห็นด้วยและไม่สนับสนุนไม่ให้ความสนใจกับประสิทธิภาพของรันไทม์ไม่ติดตามข้อผิดพลาดของประสิทธิภาพเหล่านี้เริ่มที่จะกลายเป็นความเจ็บปวดและต่อต้านปรัชญา "นักพัฒนาก่อน" ของทับทิม? ฉันเดาว่านี่เป็นความคิดเห็นของคุณที่น้อยกว่าและมีความคิดเห็นทั่วไปเกี่ยวกับความขัดแย้งในที่สุดที่ทำให้เราใช้ทับทิม
elsurudo

4
สิ่งที่เป็นปริศนา: เอาละเราเลิกแสดงแล้วในการตัดสินใจของเราที่จะใช้ทับทิมดังนั้น "สิ่งเล็กน้อยอื่น ๆ นี้" จึงแตกต่างกันอย่างไร มันลาดลื่นไม่ใช่เหรอ? สำหรับบันทึกฉันชอบโซลูชันนี้กับคำตอบที่ยอมรับได้จากมุมมองที่สามารถอ่านได้
elsurudo

1
หากคุณใช้ Ruby 2.4+ จะใช้งานได้ง่ายยิ่งขึ้น#transform_values!ตามที่ระบุไว้โดย sschmeck ( stackoverflow.com/a/41508214/6451879 )
Finn

127

Ruby 2.4 แนะนำวิธีการHash#transform_values!ที่คุณสามารถใช้ได้

{ :a=>'a' , :b=>'b' }.transform_values! { |v| "%#{v}%" }
# => {:a=>"%a%", :b=>"%b%"} 

สิ่งที่ฉันกำลังมองหา!
Dolev

4
แน่นอนว่ายังมีHash#transform_values(ไม่มีปัง) ซึ่งไม่ได้ปรับเปลี่ยนผู้รับ มิฉะนั้นคำตอบที่ดีขอบคุณ!
iGEL

1
นี่จะลดการใช้งานของฉันreduce:-p
iGEL

86

วิธีที่ดีที่สุดในการปรับเปลี่ยนค่าของแฮชคือ

hash.update(hash){ |_,v| "%#{v}%" }

รหัสน้อยลงและความตั้งใจที่ชัดเจน เร็วกว่าเนื่องจากไม่มีการจัดสรรวัตถุใหม่นอกเหนือจากค่าที่ต้องเปลี่ยน


ไม่เป็นความจริงเลย: มีการจัดสรรสตริงใหม่ ยังเป็นทางออกที่น่าสนใจที่มีประสิทธิภาพ +1
Phrogz

@ Phrogz จุดดี; ฉันอัพเดตคำตอบแล้ว การจัดสรรค่าที่ไม่สามารถหลีกเลี่ยงได้โดยทั่วไปเพราะไม่ได้ทั้งหมดการแปลงค่าสามารถแสดงเป็น mutators gsub!เช่น
Sim

3
เช่นเดียวกับคำตอบของฉัน แต่ด้วยคำพ้องความหมายอื่นผมยอมรับว่าบ่งบอกถึงความตั้งใจที่ดีกว่าupdate merge!ฉันคิดว่านี่เป็นคำตอบที่ดีที่สุด

1
หากคุณไม่ได้ใช้kให้ใช้_แทน
sekrett

28

บิตที่อ่านได้มากขึ้นmapไปยังอาร์เรย์ของแฮชองค์ประกอบเดี่ยวและreduceด้วยmerge

the_hash.map{ |key,value| {key => "%#{value}%"} }.reduce(:merge)

2
ใช้ง่ายกว่าHash[the_hash.map { |key,value| [key, "%#{value}%"] }]
meagar

นี่เป็นวิธีที่ไม่มีประสิทธิภาพอย่างยิ่งในการอัพเดทค่า สำหรับคู่คุ้มค่าทุกมันเป็นครั้งแรกสร้างคู่Array(สำหรับmap) Hashแล้ว จากนั้นแต่ละขั้นตอนของการดำเนินการลดจะทำซ้ำ "บันทึก" Hashและเพิ่มคู่คีย์ - ค่าใหม่ลงไป อย่างน้อยใช้:merge!ในreduceการปรับเปลี่ยนสุดท้ายHashในสถานที่ และในที่สุดคุณไม่ได้แก้ไขค่าของวัตถุที่มีอยู่ แต่สร้างวัตถุใหม่ซึ่งไม่ใช่สิ่งที่คำถามถาม
Sim

มันจะคืนกลับมาnilถ้าthe_hashว่างเปล่า
DNNX

18

มีวิธีการ 'Rails way' ใหม่สำหรับงานนี้ :) http://api.rubyonrails.org/classes/Hash.html#method-i-transform_values


8
Hash#transform_valuesนอกจากนี้ยังมีทับทิม 2.4.0+ นี่ควรเป็นวิธีที่จะดำเนินการต่อจากนี้ไป
อะมีบา

1
สรุปมีให้สำหรับ Rails 5+ หรือ Ruby 2.4+
Cyril Duchon-Doris


16

วิธีการหนึ่งที่ไม่แนะนำให้เกิดผลข้างเคียงกับต้นฉบับ:

h = {:a => 'a', :b => 'b'}
h2 = Hash[h.map {|k,v| [k, '%' + v + '%']}]

แผนที่ Hash #อาจเป็นสิ่งที่น่าสนใจในการอ่านเนื่องจากอธิบายว่าทำไมHash.mapไม่ส่งคืน Hash (ซึ่งเป็นสาเหตุที่ Array ของ[key,value]คู่ผลลัพธ์ถูกแปลงเป็น Hash ใหม่) และให้แนวทางอื่นในรูปแบบทั่วไปแบบเดียวกัน

การเข้ารหัสที่มีความสุข

[คำเตือน: ฉันไม่แน่ใจว่าซีแมนทิกส์Hash.mapเปลี่ยนเป็น Ruby 2.x]


7
Matz รู้หรือไม่ว่าซีแมนHash.mapติกส์เปลี่ยนใน Ruby 2.x หรือไม่?
Andrew Grimm

1
วิธีแฮช # [] มีประโยชน์มาก แต่น่าเกลียดมาก มีวิธีที่ดีกว่าในการแปลงอาร์เรย์เป็น hash ในลักษณะเดียวกันหรือไม่?
Martijn

15
my_hash.each do |key, value|
  my_hash[key] = "%#{value}%"
end

ฉันไม่ชอบผลข้างเคียง แต่ +1 สำหรับวิธีการ :) มีeach_with_objectใน Ruby 1.9 (IIRC) ซึ่งหลีกเลี่ยงการจำเป็นต้องเข้าถึงชื่อโดยตรงและMap#mergeอาจใช้งานได้ ไม่แน่ใจว่ารายละเอียดที่ซับซ้อนแตกต่างกันอย่างไร

1
แฮชเริ่มต้นได้รับการแก้ไขซึ่งไม่เป็นไรหากคาดว่าจะมีพฤติกรรมดังกล่าว แต่อาจทำให้เกิดปัญหาที่ละเอียดได้หาก "ลืม" ฉันชอบที่จะลดความไม่แน่นอนของวัตถุ แต่อาจไม่ได้ผลเสมอไป (Ruby แทบจะไม่เป็นภาษา "ไม่มีผลข้างเคียง" ;-)

8

Hash.merge! เป็นทางออกที่สะอาดที่สุด

o = { a: 'a', b: 'b' }
o.merge!(o) { |key, value| "%#{ value }%" }

puts o.inspect
> { :a => "%a%", :b => "%b%" }

@MichieldeMare ฉันขอโทษฉันไม่ได้ทดสอบอย่างละเอียดพอ คุณถูกต้องบล็อกต้องใช้พารามิเตอร์สองตัว การแก้ไข

5

หลังจากทดสอบด้วย RSpec ดังนี้:

describe Hash do
  describe :map_values do
    it 'should map the values' do
      expect({:a => 2, :b => 3}.map_values { |x| x ** 2 }).to eq({:a => 4, :b => 9})
    end
  end
end

คุณสามารถใช้ Hash # map_values ​​ดังนี้:

class Hash
  def map_values
    Hash[map { |k, v| [k, yield(v)] }]
  end
end

ฟังก์ชั่นนั้นสามารถใช้ได้ดังนี้:

{:a=>'a' , :b=>'b'}.map_values { |v| "%#{v}%" }
# {:a=>"%a%", :b=>"%b%"}

1

หากคุณอยากรู้ว่าตัวแปรแบบไหนที่เร็วที่สุดที่นี่:

Calculating -------------------------------------
inplace transform_values! 1.265k  0.7%) i/s -      6.426k in   5.080305s
      inplace update      1.300k  2.7%) i/s -      6.579k in   5.065925s
  inplace map reduce    281.367   1.1%) i/s -      1.431k in   5.086477s
      inplace merge!      1.305k  0.4%) i/s -      6.630k in   5.080751s
        inplace each      1.073k  0.7%) i/s -      5.457k in   5.084044s
      inplace inject    697.178   0.9%) i/s -      3.519k in   5.047857s
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.