จะเปลี่ยนค่า Hash ได้อย่างไร?


126

ฉันต้องการที่จะใช้แทนกันได้ในกัญชาด้วยvaluevalue.some_method

ตัวอย่างเช่นสำหรับแฮชธรรมดา:

{"a" => "b", "c" => "d"}` 

ทุกค่าควรเป็น.upcased ดังนั้นจึงดูเหมือนว่า:

{"a" => "B", "c" => "D"}

ฉันพยายาม#collectและ#mapได้รับอาร์เรย์กลับมาเสมอ มีวิธีที่สวยงามในการทำเช่นนี้หรือไม่?

UPDATE

ฉันลืมไปแล้ว: แฮชอยู่ในตัวแปรอินสแตนซ์ซึ่งไม่ควรเปลี่ยนแปลง ฉันต้องการแฮชใหม่ที่มีค่าที่เปลี่ยนแปลง แต่ไม่ต้องการกำหนดตัวแปรนั้นอย่างชัดเจนแล้ววนซ้ำในการเติมแฮช สิ่งที่ต้องการ:

new_hash = hash.magic{ ... }

ตัวอย่างที่สองในคำตอบของฉันส่งคืนแฮชใหม่ แสดงความคิดเห็นหากคุณต้องการให้ฉันขยายความมหัศจรรย์ที่ #inject กำลังทำอยู่
kch

คำตอบ:


218
my_hash.each { |k, v| my_hash[k] = v.upcase } 

หรือหากคุณต้องการดำเนินการโดยไม่ทำลายและส่งคืนแฮชใหม่แทนการแก้ไขmy_hash:

a_new_hash = my_hash.inject({}) { |h, (k, v)| h[k] = v.upcase; h } 

เวอร์ชันล่าสุดนี้มีประโยชน์เพิ่มเติมที่คุณสามารถเปลี่ยนคีย์ได้เช่นกัน


26
คำแนะนำแรกไม่เหมาะสม การแก้ไของค์ประกอบที่แจกแจงได้ในขณะที่การทำซ้ำจะทำให้เกิดพฤติกรรมที่ไม่ได้กำหนดซึ่งใช้ได้ในภาษาอื่นด้วย นี่คือคำตอบจาก Matz (เขาตอบกลับตัวอย่างโดยใช้ Array แต่คำตอบเป็นแบบทั่วไป): j.mp/tPOh2U
Marcus

4
@Marcus ฉันยอมรับว่าโดยทั่วไปควรหลีกเลี่ยงการดัดแปลงดังกล่าว แต่ในกรณีนี้อาจปลอดภัยเนื่องจากการแก้ไขไม่ได้เปลี่ยนการทำซ้ำในอนาคต ตอนนี้ถ้ามีการกำหนดคีย์อื่น (ที่ไม่ใช่คีย์ที่ส่งไปยังบล็อก) ก็จะมีปัญหา
Kelvin

36
@Adam @kch each_with_objectเป็นเวอร์ชันที่สะดวกกว่าinjectเมื่อคุณไม่ต้องการปิดกั้นด้วยแฮช:my_hash.each_with_object({}) {|(k, v), h| h[k] = v.upcase }
Kelvin

6
นอกจากนี้ยังมีไวยากรณ์ที่ประณีตมากในการแปลงรายการคู่เป็นแฮชเพื่อให้คุณสามารถเขียนได้a_new_hash = Hash[ my_hash.map { |k, v| [k, v.upcase] } ]
เขียนใหม่

2
ด้วย Ruby 2 คุณสามารถเขียน.to_h
โรมัน - โรมัน

101

เนื่องจากruby 2.4.0คุณสามารถใช้Hash#transform_valuesวิธีดั้งเดิม:

hash = {"a" => "b", "c" => "d"}
new_hash = hash.transform_values(&:upcase)
# => {"a" => "B", "c" => "D"}

นอกจากนี้ยังมีHash#transform_values!รุ่นทำลายล้าง


5
ยังมาพร้อมกับราง 4.2.1 apidock.com/rails/v4.2.1/Hash/transform_values
Coda Chang

35

คุณสามารถรวบรวมค่าและแปลงจาก Array เป็น Hash ได้อีกครั้ง

แบบนี้:

config = Hash[ config.collect {|k,v| [k, v.upcase] } ]

23

สิ่งนี้จะทำ:

my_hash.each_with_object({}) { |(key, value), hash| hash[key] = value.upcase }

ตรงข้ามกับinjectข้อดีคือคุณไม่จำเป็นต้องส่งคืนแฮชอีกครั้งในบล็อก


ฉันชอบที่วิธีนี้ไม่แก้ไขแฮชดั้งเดิม
Christian Di Lorenzo

10

ลองใช้ฟังก์ชันนี้:

h = {"a" => "b", "c" => "d"}
h.each{|i,j| j.upcase!} # now contains {"a" => "B", "c" => "D"}.

5
นั่นเป็นสิ่งที่ดี แต่ควรสังเกตว่ามันใช้ได้ก็ต่อเมื่อ j มีวิธีการทำลายล้างที่กระทำต่อตัวมันเอง ดังนั้นจึงไม่สามารถจัดการกับค่าทั่วไปบางกรณีได้
kch

จุดดีและด้วยการอัปเดตของเขาโซลูชันที่สองของคุณ (ไม่ทำลายล้าง) เป็นวิธีที่ดีที่สุด
Chris Doggett

10

มีวิธีการในActiveSupport v4.2.0 เรียกว่าtransform_valuesและโดยพื้นฐานแล้วจะดำเนินการบล็อกสำหรับคู่คีย์ - ค่า - คู่

เนื่องจากพวกเขาทำมันด้วยeachฉันคิดว่าไม่มีวิธีใดดีไปกว่าการวนซ้ำ

hash = {sample: 'gach'}

result = {}
hash.each do |key, value|
  result[key] = do_stuff(value)
end

ปรับปรุง:

ตั้งแต่ Ruby 2.4.0 คุณสามารถใช้#transform_valuesและ#transform_values!.


2

คุณอาจต้องการก้าวไปอีกขั้นและทำสิ่งนี้กับแฮชที่ซ้อนกัน แน่นอนว่าสิ่งนี้เกิดขึ้นในปริมาณที่ยุติธรรมกับโครงการ Rails

นี่คือรหัสบางส่วนเพื่อให้แน่ใจว่าแฮชพารามิเตอร์อยู่ใน UTF-8:

  def convert_hash hash
    hash.inject({}) do |h,(k,v)|
      if v.kind_of? String
        h[k] = to_utf8(v) 
      else
        h[k] = convert_hash(v)
      end
      h
    end      
  end    

  # Iconv UTF-8 helper
  # Converts strings into valid UTF-8
  #
  # @param [String] untrusted_string the string to convert to UTF-8
  # @return [String] your string in UTF-8
  def to_utf8 untrusted_string=""
    ic = Iconv.new('UTF-8//IGNORE', 'UTF-8')
    ic.iconv(untrusted_string + ' ')[0..-2]
  end  

1

ฉันทำสิ่งนี้:

new_hash = แฮช [* original_hash.collect {| คีย์ค่า | [คีย์ค่า + 1]} แบน]

สิ่งนี้ช่วยให้คุณมีสิ่งอำนวยความสะดวกในการแปลงคีย์หรือค่าผ่านนิพจน์ใด ๆ ด้วย (และแน่นอนว่ามันไม่ทำลาย)


1

Ruby มีtapวิธีการ ( 1.8.7 , 1.9.3และ2.1.0 ) ที่มีประโยชน์มากสำหรับสิ่งต่างๆเช่นนี้

original_hash = { :a => 'a', :b => 'b' }
original_hash.clone.tap{ |h| h.each{ |k,v| h[k] = v.upcase } }
# => {:a=>"A", :b=>"B"}
original_hash # => {:a=>"a", :b=>"b"}

1

ทางรถไฟที่เฉพาะเจาะจง

ในกรณีที่มีคนต้องการเพียงแค่เรียกto_smethod ไปยังแต่ละค่าและไม่ได้ใช้ Rails 4.2 (ซึ่งรวมถึงtransform_valuesmethod link ) คุณสามารถทำสิ่งต่อไปนี้:

original_hash = { :a => 'a', :b => BigDecimal('23.4') }
#=> {:a=>"a", :b=>#<BigDecimal:5c03a00,'0.234E2',18(18)>}
JSON(original_hash.to_json)
#=> {"a"=>"a", "b"=>"23.4"}

หมายเหตุ: จำเป็นต้องใช้ไลบรารี 'json'

หมายเหตุ 2: สิ่งนี้จะเปลี่ยนคีย์ให้เป็นสตริงเช่นกัน


1

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

Altohugh สิ่งนี้ จำกัด เฉพาะในกรณีที่ค่าเป็นสตริงและด้วยเหตุนี้จึงไม่ได้ตอบคำถามทั้งหมดฉันคิดว่ามันจะมีประโยชน์กับใครบางคน


1
new_hash = old_hash.merge(old_hash) do |_key, value, _value|
             value.upcase
           end

# old_hash = {"a" => "b", "c" => "d"}
# new_hash = {"a" => "B", "c" => "D"}

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