Dizeleri karma içindeki sembollere dönüştürmenin en iyi yolu


250

Bir karma içindeki tüm tuşları Ruby'deki dizelerden sembollere dönüştürmenin (en hızlı / en temiz / anlaşılır) yolu nedir?

Bu YAML ayrıştırılırken kullanışlıdır.

my_hash = YAML.load_file('yml')

Kullanabilmek istiyorum:

my_hash[:key] 

Ziyade:

my_hash['key']


80
hash.symbolize_keysve hash.deep_symbolize_keysRails kullanıyorsanız işi yapın.
Zaz

Josh, bir cevaba yorumunu yazsaydın, sana oy verirdim. 'raylar' gerektirir; hash.deep_symbolize_keys irb veya pry'de oldukça iyi çalışır. : D
Douglas G. Allen

Yanıtlar:


239

In Ruby> = 2.5 ( docs ) kullanabilirsiniz:

my_hash.transform_keys(&:to_sym)

Eski Ruby sürümünü mü kullanıyorsunuz? İşte hash'ı, anahtarları sembolize edilmiş olarak yenisine kopyalayacak bir astar:

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

İle Raylar kullanabilirsiniz:

my_hash.symbolize_keys
my_hash.deep_symbolize_keys 

5
Ah, belirsiz olduğu için özür dilerim - enjeksiyon arayanı değiştirmez. Yapmanız gereken my_hash = my_hash.inject ({}) {| memo, (k, v) | not [k.to_sym] = v; memo}
Sarah Mei

4
Tam da aradığım şey buydu. Biraz değiştirdim ve yuvalanmış karmalarda semboller oluşturmak için bazı çizgiler ekledim. Eğer ilgileniyorsanız buraya bir göz atın: any-where.de/blog/ruby-hash-convert-string-keys-to-symbols
Matt

37
Ruby 1.9'da şöyle kullanabilirsiniz each_with_object:my_hash.each_with_object({}){|(k,v), h| h[k.to_sym] = v}
sgtFloyd

8
bu özyinelemeli karmaları işlemez ... Bir kerelik bir yer bulun, ancak KURU için değil.
baash05

8
@BryanM. Bu tartışmaya çok geç geldim :-) ama aynı zamanda sonunda .tapgeçme ihtiyacını ortadan kaldırmak için yöntemi kullanabilirsiniz memo. Tüm çözümlerin temiz bir sürümünü oluşturdum (özyinelemeli olanlar da) gist.github.com/Integralist/9503099
Integralist

307

Rails kullanıyorsanız daha iyi bir yöntem:

parametreler. symbolize_keys

Son.

Eğer değilseniz, sadece kodlarını sökün (ayrıca bağlantıdadır):

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


45
İç içe karmaları sembolize etmez.
Oma

3
Bağlantıyı symbolize_keysyeni ve çalışan (Rails 3) URL ile değiştirdim. Başlangıçta URL'sini düzelttim to_options, ancak bu bağlantıda sıfır belge var. symbolize_keysaslında bir açıklaması var, bu yüzden bunun yerine kullandım.
Craig Walker

23
deep_symbolize_keys! . Works on rails 2+
mastaBlasta

14
Tersini nasıl yapacağını merak edenler için hash.stringify_keysçalışıyor.
Nick

112

Ruby'deki belirli YAML durumunda, tuşlar ' :' ile başlıyorsa , otomatik olarak sembol olarak stajyer olurlar.

'yaml' gerektir
'pp' gerektir
yaml_str = "
bağlantıları:
  - host: host1.example.com
    bağlantı noktası: 10000
  - host: host2.example.com
    liman: 20000
"
yaml_sym = "
: Bağlantıları:
  -: host: host1.example.com
    : bağlantı noktası: 10000
  -: host: host2.example.com
    : liman: 20000
"
pp yaml_str = YAML.load (yaml_str)
yaml_str.keys.first.class dosyasını koyar
pp yaml_sym = YAML.load (yaml_sym)
yaml_sym.keys.first.class dosyasını koyar

Çıktı:

# /opt/ruby-1.8.6-p287/bin/ruby ~ / test.rb
{ "Bağlantıları" =>
  [{"bağlantı noktası" => 10000, "ana bilgisayar" => "ana1.example.com"},
   {"bağlantı noktası" => 20000, "ana bilgisayar" => "ana2.example.com"}]}
sicim
{: Bağlantılar =>
  [{: port => 10000,: host => "host1.example.com"},
   {: port => 20000,: host => "host2.example.com"}]}
sembol

15
Tatlı! YAML#load_fileHer anahtarı iki nokta üst üste ile başlatmak zorunda kalmadan dizeler yerine tüm anahtarları simgelere varsayılan olarak ayarlamanın bir yolu var mı ?
ma11hew28

63

Daha da keskin:

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

6
Bu bariz bir seçim gibi görünüyor.
Casey Watson

12
İç içe karmaları sembolize etmez.
rgtk

15
Daha okunabilir bir versiyon:my_hash.map { |k, v| [k.to_sym, v] }.to_h
Dennis

60

Rails kullanıyorsanız, çok daha basittir - HashWithIndifferentAccess kullanabilir ve anahtarlara hem String hem de Symbols olarak erişebilirsiniz:

my_hash.with_indifferent_access 

Ayrıca bakınız:

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


Veya Ruby Core ve Standart Kütüphane sınıflarına birçok uzantı içeren müthiş "Facets of Ruby" Gemini kullanabilirsiniz.

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

ayrıca bkz: http://rubyworks.github.io/rubyfaux/?doc=http://rubyworks.github.io/facets/docs/facets-2.9.3/core.json#api-class-Hash


2
Aslında tam tersi. Sembolden bir dizeye dönüşür. Bir sembole dönüştürmek için my_hash.symbolize_keys kullanın
Espen

#symbolize_keys yalnızca Rails'te çalışır - düz Ruby / irb'de değil. Ayrıca #symbolize_keys öğesinin derin yuvalanmış karmalar üzerinde çalışmadığını unutmayın.
Tilo

54

Yana Ruby 2.5.0kullanabilirsiniz Hash#transform_keysya Hash#transform_keys!.

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

Ayrıca transform_values apidock.com/rails/Hash/transform_values ile çalışır . Bu gerçekten hoş, çünkü stringify_keysveya ile olduğu gibi değerleri değiştirmek için eşdeğer görünmüyor symbolize_keys.
Mike Vallano

anahtarları derinleştirmenin bir yolu var
Abhay Kumar

Bu 2018'den sonra seçilen çözüm olmalı.
Ivan Wang


26

İşte bir nesneyi derinlemesine sembolize etmenin bir yolu

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

güzel bir, ben deep_symbolize yeniden adlandırmak bile bu ile gideceğim :)
PierrOz


19

Json kullanıyorsanız ve bunu karma olarak kullanmak istiyorsanız, Ruby'de çekirdekte yapabilirsiniz:

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

symbolize_names : true değerine ayarlanırsa, JSON nesnesindeki adların (anahtarların) sembollerini döndürür. Aksi takdirde dizeler döndürülür. Dizeler varsayılan değerdir.

Doc: Json # ayrıştırma symbolize_names


symbol_hash = JSON.parse(JSON.generate(YAML.safe_load(FILENAME)), symbolize_names: true)bir YAML dosyasından geliyorsa, semboller olarak iç içe anahtarlarla hızlı bir şekilde karma elde etmenin oldukça DRY (ancak verimsiz) bir yoludur.
Cameron Gagnon

12

params.symbolize_keysde çalışacak. Bu yöntem, karma anahtarlarını sembollere dönüştürür ve yeni bir karma döndürür.


26
Bu yöntem temel Ruby değildir. Bir Rails yöntemidir.
Ricardo Acras

10

@İgorsales yanıtında bir değişiklik

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

Yanıtları tarayan kişiler için nesneyi neden değiştirdiğinizi eklerseniz yardımcı olur.
Dbz

9

Rails'te şunları kullanabilirsiniz:

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

Şuna dönüştürür:

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

3
deep_symbolize_keysRails ' Hash uzantısına eklenir , ancak Ruby çekirdeğinin bir parçası değildir.
Ryan Crispin Heneise


7

Bu, iç içe geçmiş karma öğeler için tek astarım

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, sadece Raylar için çalışıyor. Bu durumda HashWithIndifferentAccess daha iyi bir alternatif olabilir.
Adam Grant

6

Durumda sebebi bunu yapmak gerekir veri aslen JSON'dan geldi, çünkü sadece ileterek bu ayrıştırma herhangi atlamak olduğunu :symbolize_namesJSON sindirerek üzerine seçeneği.

Ray gerekmez ve Ruby> 1.9 ile çalışır

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

4

Tembel olabilir ve aşağıdakileri sarabilirsiniz lambda:

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

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

Ama bu sadece karmadan okumak için işe yarar - yazmak değil.

Bunu yapmak için şunu kullanabilirsiniz: Hash#merge

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

İnit bloğu, anahtarları istek üzerine bir kez dönüştürür, ancak sembol sürümüne eriştikten sonra anahtarın dize sürümünün değerini güncellerseniz, sembol sürümü güncellenmez.

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}

Ayrıca init bloğunun karmayı güncellememesini sağlayabilirsiniz, bu da sizi bu tür hatalardan koruyacaktır, ancak yine de tam tersine karşı savunmasız kalacaksınız - sembol sürümünün güncellenmesi dize sürümünü güncellemez:

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}

Bunlara dikkat etmeniz gereken şey, iki anahtar form arasında geçiş yapmaktır. Biriyle sopa.


3

Aşağıdaki gibi bir şey ister misiniz?

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

Bu karmayı kopyalar, ancak çoğu zaman bunu umursamazsınız. Muhtemelen tüm verileri kopyalamadan bunu yapmanın bir yolu vardır.


3

daha kısa bir tek katlı fwiw:

my_hash.inject({}){|h,(k,v)| h.merge({ k.to_sym => v}) }

2

Buna ne dersin:

my_hash = HashWithIndifferentAccess.new(YAML.load_file('yml'))

# my_hash['key'] => "val"
# my_hash[:key]  => "val"

2

Bu, mrubyherhangi bir symbolize_keysyöntemi tanımlayan ve kullanmayan kişiler içindir :

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

Yöntem:

  • yalnızca şu tuşları sembolize eder: String
  • bir dizeyi sembolize etmek bazı bilgileri kaybetmek anlamına gelirse (karma kısmının üzerine yaz) RuntimeError
  • özyinelemeli hash'ları da sembolize etmek
  • sembolize edilmiş karmayı döndür
  • yerinde çalışıyor!

1
Senin yönteminde bir yazım hatası var, unuttun !in symbolize_keys. Aksi takdirde iyi çalışır.
Ka Mok

1

Değiştirmek istediğimiz dizi.

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

Boş bir dizi olarak yeni bir değişken yapın, böylece içindeki sembolleri ".push" edelim.

semboller = []

İşte burada bloklu bir yöntem tanımlıyoruz.

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

Kod sonu.

Yani bu muhtemelen dizeleri Ruby'deki dizilerinizdeki sembollere dönüştürmenin en kolay yoludur. Bir dizi dizesi yapın, ardından yeni bir değişken yapın ve değişkeni boş bir diziye ayarlayın. Ardından, ".each" yöntemiyle oluşturduğunuz ilk dizideki öğeleri seçin. Ardından, yeni dizinizdeki tüm öğeleri ".push" yapmak için bir blok kodu kullanın ve tüm öğeleri sembollere dönüştürmek için ".intern veya .to_sym" kullanın.

Semboller daha hızlıdır çünkü kodunuzda daha fazla bellek tasarrufu sağlarlar ve yalnızca bir kez kullanabilirsiniz. Semboller en büyük hash tuşları için kullanılır. Ben en iyi yakut programcı değilim ama bu kod biçimi bana çok yardımcı oldu. Kimse daha iyi bir yol biliyorsa lütfen paylaşın ve karma için de bu yöntemi kullanabilirsiniz!


2
Soru dizilerle değil karmalarla ilgiliydi.
Richard_G

1

Vanilya yakut çözeltisi istiyorsanız ve ActiveSupportburaya erişemediğiniz için derin sembolize çözümdür (öncekilere çok benzer)

    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'dan başlayarak symbolize_names: seçeneğini ekleyebilirsiniz.

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

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

Not: 3.0'dan daha düşük bir Psych sürümüne sahipseniz symbolize_names:sessizce göz ardı edilir.

Benim Ubuntu 18.04 ruby ​​2.5.1p57 ile kutudan çıkıyor


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}

Bunu adaha da keskin hale getirmek için blok argümanını ayrıştırmak için parantez içine alabilirsiniz . Örneğin cevabıma bakın.
Michael Barton

0

Bu tam olarak bir astar değildir, ancak tüm dize anahtarlarını sembollere, ayrıca iç içe olanlara dönüştürür:

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 kullanmadığımda bu tek astarı seviyorum, çünkü o zaman işlerken ikinci bir karma yapmak ve iki veri kümesini tutmak zorunda değilim:

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, silinen anahtarın değerini döndürür


0

Facets 'Hash # deep_rekey de iyi bir seçenektir, özellikle:

  • projenizdeki fasetlerden diğer şekerler için kullanım bulursanız,
  • şifreli tek satırlara göre kod okunabilirliğini tercih ederseniz.

Örneklem:

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

0

Ruby'de bunu karmadaki dize anahtarlarını sembollere dönüştürmenin en basit ve kolay anlaşılır yolu olarak görüyorum:

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

Karmadaki her anahtar için, onu karmadan kaldıran (ayrıca sil , silinen anahtarla ilişkili değeri döndürür) silme adını veririz ve bunu hemen sembolize tuşa eşitleriz.


0

Önceki çözümlere benzer ancak biraz farklı yazılmıştır.

  • Bu, iç içe geçmiş ve / veya dizileri olan bir karmaya izin verir.
  • Anahtarların bir dizeye bonus olarak dönüştürülmesini sağlayın.
  • Kod, aktarılan karma değerini değiştirmez.

    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
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.