วิธีที่ดีที่สุดในการแปลงสตริงเป็นสัญลักษณ์ในแฮช


250

อะไรคือวิธีที่เร็วที่สุด / สะอาด / ตรงไปตรงมาในการแปลงคีย์ทั้งหมดในแฮชจากสตริงเป็นสัญลักษณ์ในรูบี

นี่จะเป็นประโยชน์เมื่อแยกวิเคราะห์ YAML

my_hash = YAML.load_file('yml')

ฉันต้องการใช้:

my_hash[:key] 

ค่อนข้างมากกว่า:

my_hash['key']


80
hash.symbolize_keysและhash.deep_symbolize_keysทำงานถ้าคุณใช้ Rails
Zaz

ถ้าคุณจะใส่ความคิดเห็นของคุณลงในคำตอบฉันก็จะโหวตให้คุณ require 'rails'; hash.deep_symbolize_keys ทำงานได้ดีใน irb หรือ pry : D
Douglas G. Allen

คำตอบ:


239

ในRuby> = 2.5 ( เอกสาร ) คุณสามารถใช้:

my_hash.transform_keys(&:to_sym)

ใช้รุ่น Ruby รุ่นเก่าหรือไม่ นี่คือสายการบินหนึ่งที่จะคัดลอกแฮชลงในอันใหม่ที่มีปุ่มสัญลักษณ์:

my_hash = my_hash.inject({}){|memo,(k,v)| memo[k.to_sym] = v; memo}

ด้วยRailsคุณสามารถใช้:

my_hash.symbolize_keys
my_hash.deep_symbolize_keys 

5
อ่าขอโทษที่ไม่ชัดเจน - ฉีดไม่ได้แก้ไขผู้โทร คุณต้องทำ my_hash = my_hash.inject ({}) {| บันทึก, (k, v) | memo [k.to_sym] = v; บันทึก}
Sarah Mei

4
นั่นคือสิ่งที่ฉันกำลังมองหา ฉันแก้ไขมันเล็กน้อยและเพิ่มบางบรรทัดเพื่อสร้างสัญลักษณ์ในแฮชที่ตั้งอยู่ ลองดูที่นี่ถ้าคุณสนใจ: any-where.de/blog/ruby-hash-convert-string-keys-to-symbols
แมตต์

37
ใน Ruby 1.9 คุณสามารถใช้each_with_objectดังนี้:my_hash.each_with_object({}){|(k,v), h| h[k.to_sym] = v}
sgtFloyd

8
นี่ไม่ได้จัดการกับแฮ็คแบบเรียกซ้ำ ... ค้นหาแบบครั้งเดียว แต่ไม่ใช่สำหรับ DRY
baash05

8
@BryanM ฉันเข้ามาในการสนทนานี้ช้ามาก :-) แต่คุณสามารถใช้.tapวิธีการที่จะลบความต้องการที่จะผ่านmemoในตอนท้าย ฉันได้สร้างเวอร์ชันที่แก้ไขแล้วของโซลูชันทั้งหมด (โซลูชันที่เรียกซ้ำได้เช่นกัน) gist.github.com/Integralist/9503099
Integralist

307

นี่เป็นวิธีที่ดีกว่าถ้าคุณใช้ Rails:

params symbolize_keys

ตอนจบ.

หากคุณไม่ได้เป็นเพียงแค่ฉีกรหัสของพวกเขา (ยังอยู่ในลิงค์):

myhash.keys.each do |key|
  myhash[(key.to_sym rescue key) || key] = myhash.delete(key)
end

6
to_optionsเป็นนามแฝงสำหรับsybolize_keys
ma11hew28

45
จะไม่เป็นสัญลักษณ์ของแฮชที่ซ้อนกัน
oma

3
ฉันเปลี่ยนลิงค์เป็นsymbolize_keysURL ใหม่และใช้งานได้ (ราง 3) เดิมทีฉันเพิ่งแก้ไข URL สำหรับto_optionsแต่ไม่มีเอกสารประกอบที่ลิงก์นั้น symbolize_keysมีคำอธิบายจริง ๆ แล้วฉันก็ใช้มันแทน
Craig Walker

23
deep_symbolize_keys! . ทำงานบนราง 2+
mastaBlasta

14
สำหรับผู้ที่อยากรู้อยากเห็นวิธีการย้อนกลับได้hash.stringify_keysผล
นิค

112

สำหรับกรณีเฉพาะของ YAML ใน Ruby หากคีย์เริ่มต้นด้วย ' :' พวกเขาจะถูก interned เป็นสัญลักษณ์โดยอัตโนมัติ

ต้องการ 'yaml'
ต้องการ 'pp'
yaml_str = "
การเชื่อมต่อ:
  - โฮสต์: host1.example.com
    พอร์ต: 10,000
  - โฮสต์: host2.example.com
    พอร์ต: 20,000
"
yaml_sym = "
: การเชื่อมต่อ:
  -: host: host1.example.com
    : พอร์ต: 10000
  -: host: host2.example.com
    : พอร์ต: 20,000
"
pp yaml_str = YAML.load (yaml_str)
วาง yaml_str.keys.first.class
pp yaml_sym = YAML.load (yaml_sym)
วาง yaml_sym.keys.first.class

เอาท์พุท:

# /opt/ruby-1.8.6-p287/bin/ruby ~ / test.rb
{ "การเชื่อมต่อ" =>
  [{"พอร์ต" => 10000, "host" => "host1.example.com"},
   {"พอร์ต" => 20000, "โฮสต์" => "host2.example.com"}]}
เชือก
{: การเชื่อมต่อ =>
  [{: พอร์ต => 10000,: host => "host1.example.com"},
   {: พอร์ต => 20000,: host => "host2.example.com"}]}
สัญลักษณ์

15
หวาน! มีวิธีการตั้งค่าYAML#load_fileเริ่มต้นคีย์ทั้งหมดเป็นสัญลักษณ์แทนสตริงโดยไม่ต้องเริ่มต้นทุกคีย์ด้วยโคลอนหรือไม่?
ma11hew28

63

ยิ่งบทสรุป:

Hash[my_hash.map{|(k,v)| [k.to_sym,v]}]

6
ดูเหมือนว่าจะเป็นตัวเลือกที่ชัดเจน
Casey Watson

12
มันไม่ได้เป็นสัญลักษณ์ของแฮชซ้อนกัน
rgtk

15
รุ่นที่อ่านได้มากขึ้น:my_hash.map { |k, v| [k.to_sym, v] }.to_h
Dennis

60

หากคุณใช้ Rails จะง่ายกว่ามาก - คุณสามารถใช้ HashWithIndifferentAccess และเข้าถึงกุญแจได้ทั้งแบบ String และสัญลักษณ์:

my_hash.with_indifferent_access 

ดูสิ่งนี้ด้วย:

http://api.rubyonrails.org/classes/ActiveSupport/HashWithIndifferentAccess.html


หรือคุณสามารถใช้ Gem "Facets of Ruby" ที่ยอดเยี่ยมซึ่งมีส่วนขยายจำนวนมากสำหรับคลาส Ruby Core และ Standard Library

  require 'facets'
  > {'some' => 'thing', 'foo' => 'bar'}.symbolize_keys
    =>  {:some=>"thing", :foo=>"bar}

ดูเพิ่มเติมที่: http://rubyworks.github.io/rubyfaux/?doc=http://rubyworks.github.io/facets/docs/facets-2.9.3/core.json#api-class-Hash


2
จริงๆแล้วมันตรงกันข้าม มันจะแปลงจากสัญลักษณ์เป็นสตริง ในการแปลงเป็นสัญลักษณ์ให้ใช้ my_hash.symbolize_keys
Espen

#symbolize_keys ใช้งานได้ใน Rails เท่านั้นไม่ใช่ใน Ruby / irb ธรรมดา โปรดทราบด้วยว่า #symbolize_keys ไม่สามารถใช้งานกับแฮชที่ซ้อนกันได้
Tilo

54

ตั้งแต่Ruby 2.5.0คุณสามารถใช้หรือHash#transform_keysHash#transform_keys!

{'a' => 1, 'b' => 2}.transform_keys(&:to_sym) #=> {:a => 1, :b => 2}

ยังทำงานร่วมกับ transform_values apidock.com/rails/Hash/transform_values นั่นเป็นเรื่องดีจริงๆเพราะมีไม่ดูเหมือนจะเป็นเทียบเท่าสำหรับการปรับเปลี่ยนค่าเหมือนมีด้วยหรือstringify_keys symbolize_keys
Mike Vallano

จะมีวิธีใดในการใช้สัญลักษณ์สัญลักษณ์ลึก
Abhay Kumar

นี่ควรจะเป็นทางเลือกที่ได้รับหลังจากปี 2018
Ivan Wang


26

นี่คือวิธีในการทำสัญลักษณ์วัตถุให้ลึก

def symbolize(obj)
    return obj.inject({}){|memo,(k,v)| memo[k.to_sym] =  symbolize(v); memo} if obj.is_a? Hash
    return obj.inject([]){|memo,v    | memo           << symbolize(v); memo} if obj.is_a? Array
    return obj
end

ดีหนึ่งฉันจะไปกับคนนี้แม้ว่าฉันจะเปลี่ยนชื่อ deep_symbolize :)
PierrOz

20

ฉันชอบอัญมณีMash

คุณสามารถทำmash['key']หรือmash[:key]หรือหรือmash.key


2
นั่นเป็นอัญมณีที่ยอดเยี่ยมมาก! ทำให้การทำงานกับแฮชสะดวกสบายมาก ขอบคุณสำหรับสิ่งนี้!
asaaki

เรียบง่ายมาก การพัฒนาใหม่ของโครงการนี้กำลังดำเนินการต่อใน Hashie ( github.com/intridea/hashie ) แต่ก็ยังคงใช้งานได้ในลักษณะเดียวกัน: github.com/intridea/hashie#keyconversion
jpalmieri

19

หากคุณใช้ json และต้องการใช้เป็นแฮชใน core Ruby คุณสามารถทำได้:

json_obj = JSON.parse(json_str, symbolize_names: true)

symbolize_names : ถ้าตั้งค่าเป็น True จะส่งคืนสัญลักษณ์สำหรับชื่อ (คีย์) ในวัตถุ JSON มิฉะนั้นจะถูกส่งกลับสตริง สตริงเป็นค่าเริ่มต้น

Doc: Json # parse symbolize_names


symbol_hash = JSON.parse(JSON.generate(YAML.safe_load(FILENAME)), symbolize_names: true)เป็นวิธี DRY (แต่ไม่มีประสิทธิภาพ) ที่น่ารักเพื่อรับแฮชพร้อมกับคีย์ที่ซ้อนกันเป็นสัญลักษณ์อย่างรวดเร็วหากมาจากไฟล์ YAML
Cameron Gagnon

12

params.symbolize_keysยังจะทำงาน วิธีนี้เปลี่ยนแป้นแฮชเป็นสัญลักษณ์และส่งคืนแฮชใหม่


26
วิธีการนั้นไม่ได้เป็นแกนทับทิม มันเป็นวิธีการทางรถไฟ
Ricardo Acras

10

การแก้ไขคำตอบ @igorsales

class Object
  def deep_symbolize_keys
    return self.inject({}){|memo,(k,v)| memo[k.to_sym] = v.deep_symbolize_keys; memo} if self.is_a? Hash
    return self.inject([]){|memo,v    | memo           << v.deep_symbolize_keys; memo} if self.is_a? Array
    return self
  end
end

มันจะมีประโยชน์ถ้าคุณรวมว่าทำไมคุณถึงแก้ไขวัตถุสำหรับคนที่สแกนคำตอบ
Dbz

9

ใน Rails คุณสามารถใช้:

{'g'=> 'a', 2 => {'v' => 'b', 'x' => { 'z' => 'c'}}}.deep_symbolize_keys!

เปลี่ยนเป็น:

{:g=>"a", 2=>{:v=>"b", :x=>{:z=>"c"}}}

3
deep_symbolize_keysถูกเพิ่มในส่วนขยายแฮชของ Rails แต่ไม่ได้เป็นส่วนหนึ่งของคอร์รูบี
Ryan Crispin Heneise

7

คำตอบมากมายที่นี่ แต่ฟังก์ชั่นรางวิธีหนึ่งคือ hash.symbolize_keys


1
คุณสามารถใช้กับ Rails ได้เท่านั้น: api.rubyonrails.org/classes/Hash.html#method-i-symbolize_keysแฮชเริ่มต้นไม่มี: docs.ruby-lang.org/en/2.0.0/Hash html
Eduardo Santana

1
ทางออกสำหรับรางเท่านั้น
rodvlopes

7

นี่เป็นซับในสำหรับแฮชซ้อนกัน

def symbolize_keys(hash)
  hash.each_with_object({}) { |(k, v), h| h[k.to_sym] = v.is_a?(Hash) ? symbolize_keys(v) : v }
end

FYI ใช้งานได้กับ Rails เท่านั้น ในกรณีนี้ HashWithIndifferentAccess อาจเป็นทางเลือกที่ดีกว่า
Adam Grant

6

ในกรณีที่เหตุผลที่คุณต้องทำคือเนื่องจากข้อมูลของคุณมาจาก JSON คุณสามารถข้ามการแยกวิเคราะห์ใด ๆ โดยเพียงผ่านใน:symbolize_namesตัวเลือกในการบริโภค JSON

ไม่จำเป็นต้องใช้ Rails และทำงานกับ Ruby> 1.9

JSON.parse(my_json, :symbolize_names => true)

4

คุณอาจขี้เกียจและห่อไว้ในlambda:

my_hash = YAML.load_file('yml')
my_lamb = lambda { |key| my_hash[key.to_s] }

my_lamb[:a] == my_hash['a'] #=> true

แต่วิธีนี้ใช้ได้กับการอ่านจากแฮชเท่านั้น - ไม่ใช่การเขียน

ในการทำเช่นนั้นคุณสามารถใช้ Hash#merge

my_hash = Hash.new { |h,k| h[k] = h[k.to_s] }.merge(YAML.load_file('yml'))

บล็อก init จะแปลงคีย์แบบครั้งเดียวตามต้องการแม้ว่าคุณจะอัปเดตค่าสำหรับรุ่นสตริงของคีย์หลังจากเข้าถึงเวอร์ชันสัญลักษณ์เวอร์ชันสัญลักษณ์จะไม่ถูกอัพเดต

irb> x = { 'a' => 1, 'b' => 2 }
#=> {"a"=>1, "b"=>2}
irb> y = Hash.new { |h,k| h[k] = h[k.to_s] }.merge(x)
#=> {"a"=>1, "b"=>2}
irb> y[:a]  # the key :a doesn't exist for y, so the init block is called
#=> 1
irb> y
#=> {"a"=>1, :a=>1, "b"=>2}
irb> y[:a]  # the key :a now exists for y, so the init block is isn't called
#=> 1
irb> y['a'] = 3
#=> 3
irb> y
#=> {"a"=>3, :a=>1, "b"=>2}

คุณสามารถมีบล็อกเริ่มต้นไม่อัปเดตแฮชซึ่งจะป้องกันคุณจากข้อผิดพลาดประเภทนั้น แต่คุณยังคงมีความเสี่ยงที่ตรงกันข้าม - การอัปเดตเวอร์ชันสัญลักษณ์จะไม่อัปเดตเวอร์ชันสตริง:

irb> q = { 'c' => 4, 'd' => 5 }
#=> {"c"=>4, "d"=>5}
irb> r = Hash.new { |h,k| h[k.to_s] }.merge(q)
#=> {"c"=>4, "d"=>5}
irb> r[:c] # init block is called
#=> 4
irb> r
#=> {"c"=>4, "d"=>5}
irb> r[:c] # init block is called again, since this key still isn't in r
#=> 4
irb> r[:c] = 7
#=> 7
irb> r
#=> {:c=>7, "c"=>4, "d"=>5}

ดังนั้นสิ่งที่ต้องระวังด้วยสิ่งเหล่านี้คือการสลับระหว่างสองรูปแบบที่สำคัญ ติดกับหนึ่ง


3

มีบางอย่างที่ต้องการทำงานต่อไปนี้หรือไม่

new_hash = Hash.new
my_hash.each { |k, v| new_hash[k.to_sym] = v }

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




2

สิ่งนี้มีไว้สำหรับผู้ที่ใช้mrubyและไม่ได้symbolize_keysกำหนดวิธีการใด ๆไว้:

class Hash
  def symbolize_keys!
    self.keys.each do |k|
      if self[k].is_a? Hash
        self[k].symbolize_keys!
      end
      if k.is_a? String
        raise RuntimeError, "Symbolizing key '#{k}' means overwrite some data (key :#{k} exists)" if self[k.to_sym]
        self[k.to_sym] = self[k]
        self.delete(k)
      end
    end
    return self
  end
end

วิธีการ:

  • เป็นสัญลักษณ์ของคีย์เท่านั้น String
  • ถ้า symbolize สตริงหมายถึงการสูญเสียข้อมูลบางส่วน (เขียนทับส่วนของแฮช) ให้เพิ่ม RuntimeError
  • เป็นสัญลักษณ์นอกจากนี้ยังมีแฮชแบบเรียกซ้ำ
  • ส่งคืนแฮชที่เป็นสัญลักษณ์
  • ทำงานในสถานที่!

1
มีการพิมพ์ผิดในการที่คุณเป็นคุณลืมใน! symbolize_keysมิฉะนั้นทำงานได้ดี
Ka Mok

1

อาร์เรย์ที่เราต้องการเปลี่ยน

strings = ["HTML", "CSS", "JavaScript", "Python", "Ruby"]

สร้างตัวแปรใหม่เป็นอาร์เรย์ว่างเพื่อให้เราสามารถ ".push" สัญลักษณ์

สัญลักษณ์ = []

ที่นี่เราจะกำหนดวิธีการด้วยบล็อก

strings.each {| x | symbols.push (x.intern)}

จุดสิ้นสุดของรหัส

ดังนั้นนี่อาจเป็นวิธีที่ตรงไปตรงมาที่สุดในการแปลงสตริงเป็นสัญลักษณ์ในอาร์เรย์ของคุณใน Ruby สร้างอาร์เรย์ของสตริงจากนั้นสร้างตัวแปรใหม่และตั้งค่าตัวแปรเป็นอาร์เรย์ว่าง จากนั้นเลือกแต่ละองค์ประกอบในอาร์เรย์แรกที่คุณสร้างด้วยวิธี ".each" จากนั้นใช้รหัสบล็อกเพื่อ ".push" องค์ประกอบทั้งหมดในอาร์เรย์ใหม่ของคุณและใช้ ".internet หรือ. to_sym" เพื่อแปลงองค์ประกอบทั้งหมดเป็นสัญลักษณ์

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


2
คำถามเกี่ยวกับแฮชไม่ใช่อาร์เรย์
Richard_G

1

หากคุณต้องการวิธีการแก้ปัญหาวานิลลาทับทิมและในขณะที่ฉันไม่สามารถเข้าถึงActiveSupportที่นี่เป็นโซลูชั่นสัญลักษณ์ลึก (คล้ายกับคนก่อนหน้า)

    def deep_convert(element)
      return element.collect { |e| deep_convert(e) } if element.is_a?(Array)
      return element.inject({}) { |sh,(k,v)| sh[k.to_sym] = deep_convert(v); sh } if element.is_a?(Hash)
      element
    end

1

เริ่มต้นที่Psych 3.0 คุณสามารถเพิ่มตัวเลือกsymbolize_names:

Psych.load("---\n foo: bar") # => {"foo"=>"bar"}

Psych.load("---\n foo: bar", symbolize_names: true) # => {:foo=>"bar"}

หมายเหตุ: หากคุณมีเวอร์ชัน Psych ต่ำกว่า 3.0 symbolize_names:จะถูกเพิกเฉยอย่างเงียบ ๆ

Ubuntu ของฉัน 18.04 รวมเอาไว้ในกล่องด้วยทับทิม 2.5.1p57


0
ruby-1.9.2-p180 :001 > h = {'aaa' => 1, 'bbb' => 2}
 => {"aaa"=>1, "bbb"=>2} 
ruby-1.9.2-p180 :002 > Hash[h.map{|a| [a.first.to_sym, a.last]}]
 => {:aaa=>1, :bbb=>2}

คุณสามารถตัดaในวงเล็บเพื่อย่อยอาร์กิวเมนต์บล็อกเพื่อทำให้คำศัพท์สั้นลง ดูคำตอบของฉันเช่น
Michael Barton

0

นี่ไม่ใช่ซับเส้นเดียว แต่เปลี่ยนเป็นคีย์สตริงทั้งหมดเป็นสัญลักษณ์รวมถึงสตริงซ้อนกัน:

def recursive_symbolize_keys(my_hash)
  case my_hash
  when Hash
    Hash[
      my_hash.map do |key, value|
        [ key.respond_to?(:to_sym) ? key.to_sym : key, recursive_symbolize_keys(value) ]
      end
    ]
  when Enumerable
    my_hash.map { |value| recursive_symbolize_keys(value) }
  else
    my_hash
  end
end

0

ฉันชอบซับในตัวนี้เมื่อฉันไม่ได้ใช้ Rails เพราะฉันไม่ต้องแฮชที่สองและเก็บข้อมูลสองชุดในขณะที่ฉันกำลังประมวลผล:

my_hash = { "a" => 1, "b" => "string", "c" => true }

my_hash.keys.each { |key| my_hash[key.to_sym] = my_hash.delete(key) }

my_hash
=> {:a=>1, :b=>"string", :c=>true}

Hash # delete ส่งคืนค่าของคีย์ที่ถูกลบ


0

Hash # deep_rekey ของ Facetsเป็นตัวเลือกที่ดีเช่นกันโดยเฉพาะ:

  • หากคุณพบว่าใช้สำหรับน้ำตาลอื่นจากแง่มุมในโครงการของคุณ
  • หากคุณต้องการให้อ่านรหัสได้ง่ายกว่า cryptical one-liners

ตัวอย่าง:

require 'facets/hash/deep_rekey'
my_hash = YAML.load_file('yml').deep_rekey

0

ในทับทิมฉันคิดว่านี่เป็นวิธีที่ง่ายและเข้าใจง่ายที่สุดในการเปลี่ยนคีย์สตริงเป็นแฮชเป็นสัญลักษณ์:

my_hash.keys.each { |key| my_hash[key.to_sym] = my_hash.delete(key)}

สำหรับแต่ละคีย์ในแฮชเราจะเรียก delete บนมันซึ่งจะลบมันออกจากแฮช (เช่น delete จะคืนค่าที่เกี่ยวข้องกับคีย์ที่ถูกลบ) และเราตั้งค่านี้ทันทีเท่ากับคีย์สัญลักษณ์


0

คล้ายกับโซลูชันก่อนหน้า แต่เขียนแตกต่างกันเล็กน้อย

  • วิธีนี้ช่วยให้แฮชที่ซ้อนอยู่และ / หรือมีอาร์เรย์
  • รับการแปลงคีย์เป็นสตริงเป็นโบนัส
  • รหัสไม่กลายพันธุ์แฮถูกส่งผ่านมา

    module HashUtils
      def symbolize_keys(hash)
        transformer_function = ->(key) { key.to_sym }
        transform_keys(hash, transformer_function)
      end
    
      def stringify_keys(hash)
        transformer_function = ->(key) { key.to_s }
        transform_keys(hash, transformer_function)
      end
    
      def transform_keys(obj, transformer_function)
        case obj
        when Array
          obj.map{|value| transform_keys(value, transformer_function)}
        when Hash
          obj.each_with_object({}) do |(key, value), hash|
            hash[transformer_function.call(key)] = transform_keys(value, transformer_function)
          end
        else
          obj
        end
      end
    end
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.