Najlepszy sposób na konwersję ciągów znaków na symbole w skrócie

250

Jaki jest (najszybszy / najczystszy / prosty) sposób konwersji wszystkich kluczy w haszu z ciągów znaków na symbole w Rubim?

Przydałoby się to podczas analizowania YAML.

my_hash = YAML.load_file('yml')

Chciałbym móc użyć:

my_hash[:key] 

Zamiast:

my_hash['key']
Bryan M.
źródło
dup ?
Ian Vaughan
80
hash.symbolize_keysi hash.deep_symbolize_keyswykonaj pracę, jeśli używasz Railsów.
Zaz
Josh, gdybyś umieścił swój komentarz w odpowiedzi, głosowałbym za tobą. wymagają „szyn”; hash.deep_symbolize_keys działa całkiem dobrze w IRB lub PRY. : D
Douglas G. Allen,

Odpowiedzi:

239

W Ruby> = 2.5 ( dokumenty ) możesz użyć:

my_hash.transform_keys(&:to_sym)

Używasz starszej wersji Ruby? Oto linijka, która skopiuje skrót do nowego z symbolami kluczy:

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

Z Railsami możesz używać:

my_hash.symbolize_keys
my_hash.deep_symbolize_keys 
Sarah Mei
źródło
5
Ach, przepraszam, że jestem niejasny - zastrzyk nie modyfikuje dzwoniącego. Musisz zrobić my_hash = my_hash.inject ({}) {| memo, (k, v) | memo [k.to_sym] = v; memo}
Sarah Mei
4
Właśnie tego szukałem. Trochę go zmodyfikowałem i dodałem kilka linii, aby nawet tworzyć symbole w zagnieżdżonych skrótach. Spójrz tutaj, jeśli jesteś zainteresowany: any-where.de/blog/ruby-hash-convert-string-keys-to-symbols
Matt
37
W Ruby 1.9 możesz używać each_with_objecttak:my_hash.each_with_object({}){|(k,v), h| h[k.to_sym] = v}
sgtFloyd
8
to nie obsługuje rekurencyjnych skrótów ... Znajdź jednorazowy, ale nie DRY.
baash05
8
@BryanM. Przyszedłem do tej dyskusji bardzo późno :-), ale możesz również użyć tej .tapmetody, aby usunąć potrzebę zaliczenia memona końcu. Stworzyłem oczyszczoną wersję wszystkich rozwiązań (także rekurencyjnych) gist.github.com/Integralist/9503099
Integralist
307

Oto lepsza metoda, jeśli używasz Railsów:

parametry. symbolize_keys

Koniec.

Jeśli nie, po prostu oderwij ich kod (znajduje się również w linku):

myhash.keys.each do |key|
  myhash[(key.to_sym rescue key) || key] = myhash.delete(key)
end
Sai
źródło
6
to_options to alias dla sybolize_keys .
ma11hew28
45
Nie będzie symbolizować zagnieżdżonych skrótów.
oma
3
Zamieniłem link symbolize_keysna nowy i działający (Rails 3) adres URL. Pierwotnie właśnie poprawiłem adres URL to_options, ale pod tym linkiem nie ma żadnej dokumentacji. symbolize_keysfaktycznie ma opis, więc użyłem go zamiast tego.
Craig Walker
23
deep_symbolize_keys! . Działa na szynach 2+
mastaBlasta
14
Dla tych, którzy są ciekawi, jak zrobić odwrotnie, hash.stringify_keysdziała.
Nick
112

W konkretnym przypadku YAML w Ruby, jeśli klawisze zaczynają się od „ :”, zostaną automatycznie internowane jako symbole.

wymagają „yaml”
wymagają „pp”
yaml_str = "
znajomości:
  - host: host1.przyklad.com
    port: 10000
  - host: host2.przyklad.com
    port: 20000
„
yaml_sym = "
:znajomości:
  -: host: host1.przyklad.com
    : port: 10000
  -: host: host2.przyklad.com
    : port: 20000
„
pp yaml_str = YAML.load (yaml_str)
stawia yaml_str.keys.first.class
pp yaml_sym = YAML.load (yaml_sym)
stawia yaml_sym.keys.first.class

Wynik:

# /opt/ruby-1.8.6-p287/bin/ruby ~ / test.rb
{„połączenia” =>
  [{„port” => 10000, „host” => „host1.przyklad.com”},
   {„port” => 20000, „host” => „host2.example.com”}]}
Strunowy
{: połączenia =>
  [{: port => 10000,: host => „host1.przyklad.com”},
   {: port => 20000,: host => „host2.example.com”}]}
Symbol
jrgm
źródło
15
Słodkie! Czy istnieje sposób na ustawienie YAML#load_filedomyślnych wszystkich klawiszy symboli zamiast ciągów bez konieczności rozpoczynania każdego klawisza dwukropkiem?
ma11hew28
63

Jeszcze bardziej zwięzłe:

Hash[my_hash.map{|(k,v)| [k.to_sym,v]}]
Michael Barton
źródło
6
To wydaje się oczywistym wyborem.
Casey Watson,
12
Nie symbolizuje zagnieżdżonych skrótów.
rgtk
15
Bardziej czytelna wersja:my_hash.map { |k, v| [k.to_sym, v] }.to_h
Dennis
60

jeśli używasz Railsów, jest to o wiele prostsze - możesz użyć HashWithIndifferentAccess i uzyskać dostęp do kluczy zarówno jako String, jak i Symbole:

my_hash.with_indifferent_access 

Zobacz też:

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


Możesz też użyć niesamowitego klejnotu „Facets of Ruby”, który zawiera wiele rozszerzeń klas Ruby Core i bibliotek standardowych.

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

patrz także: http://rubyworks.github.io/rubyfaux/?doc=http://rubyworks.github.io/facets/docs/facets-2.9.3/core.json#api-class-Hash

Tilo
źródło
2
W rzeczywistości robi to odwrotnie. Konwertuje z symbolu na ciąg. Aby przekonwertować na symbol, użyj my_hash.symbolize_keys
Espen
#symbolize_keys działa tylko w Railsach - nie w zwykłym Ruby / irb. Zauważ też, że #symbolize_keys nie działa na głęboko zagnieżdżonych skrótach.
Tilo
54

Ponieważ Ruby 2.5.0możesz użyć Hash#transform_keyslub Hash#transform_keys!.

{'a' => 1, 'b' => 2}.transform_keys(&:to_sym) #=> {:a => 1, :b => 2}
Sagar Pandya
źródło
Współpracuje również z transform_values apidock.com/rails/Hash/transform_values . To naprawdę miłe, ponieważ wydaje się, że nie ma odpowiednika do modyfikowania wartości, jak w przypadku stringify_keyslub symbolize_keys.
Mike Vallano,
jest sposób na głęboką symbolizację kluczy
Abhay Kumar
To powinno być wybrane rozwiązanie po 2018 r.
Ivan Wang,
26

Oto sposób na głęboką symbolizację obiektu

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
igorsales
źródło
fajnie, pójdę z tym, nawet jeśli zmienię jego nazwę deep_symbolize :)
PierrOz
20

Naprawdę lubię klejnot Mash .

możesz zrobić mash['key'], lub mash[:key], lubmash.key

Jaganaganowicz
źródło
2
To bardzo fajny klejnot! Sprawia, że ​​praca z hashami jest bardzo wygodna. Dzięki za to!
asaaki,
Tak pięknie. Nowe prace nad tym projektem są kontynuowane w Hashie ( github.com/intridea/hashie ), ale nadal działa prawie tak samo: github.com/intridea/hashie#keyconversion
jpalmieri
19

Jeśli używasz jsona i chcesz go używać jako skrótu, w rdzeniu Ruby możesz to zrobić:

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

symbolize_names : Jeśli ustawione na true, zwraca symbole dla nazw (kluczy) w obiekcie JSON. W przeciwnym razie zwracane są łańcuchy. Ciągi są domyślne.

Doc: Json # parsuj symbolize_names

znak
źródło
symbol_hash = JSON.parse(JSON.generate(YAML.safe_load(FILENAME)), symbolize_names: true)jest dość SUCHYM (ale nieefektywnym) sposobem szybkiego uzyskania skrótu z zagnieżdżonymi kluczami jako symbolami, jeśli pochodzi z pliku YAML.
Cameron Gagnon
12

params.symbolize_keysbędzie również działać. Ta metoda zamienia klucze skrótu w symbole i zwraca nowy skrót.

Jae Cho
źródło
26
Ta metoda nie jest rdzeniem Ruby. Jest to metoda Railsowa.
Ricardo Acras,
10

Modyfikacja odpowiedzi @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
Tony
źródło
Byłoby pomocne, gdybyś podał powód modyfikacji obiektu dla osób skanujących odpowiedzi.
Dbz
9

W Railsach możesz użyć:

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

Konwertuje na:

{:g=>"a", 2=>{:v=>"b", :x=>{:z=>"c"}}}
JayJay
źródło
3
deep_symbolize_keysjest dodany w rozszerzeniu Hash Rails , ale nie jest częścią rdzenia Ruby.
Ryan Crispin Heneise
7

To moja jedyna wkładka do zagnieżdżonych skrótów

def symbolize_keys(hash)
  hash.each_with_object({}) { |(k, v), h| h[k.to_sym] = v.is_a?(Hash) ? symbolize_keys(v) : v }
end
Nick Dobson
źródło
Do twojej wiadomości, działa tylko dla Railsów. W takim przypadku HashWithIndifferentAccess może być lepszą alternatywą.
Adam Grant
6

W przypadku, gdy powodem trzeba to zrobić, ponieważ dane pochodzi z JSON, można pominąć żadnej z tego parsowania po prostu przechodząc w :symbolize_namesopcji po spożyciu JSON.

Nie wymaga Railsów i działa z Ruby> 1.9

JSON.parse(my_json, :symbolize_names => true)
Adam Grant
źródło
4

Możesz być leniwy i owinąć to lambda:

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

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

Ale działałoby to tylko do czytania z hasza - nie do pisania.

Aby to zrobić, możesz użyć Hash#merge

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

Blok inicjujący dokona konwersji kluczy jeden raz na żądanie, ale jeśli zaktualizujesz wartość wersji ciągu klucza po uzyskaniu dostępu do wersji symbolu, wersja symbolu nie zostanie zaktualizowana.

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}

Możliwe, że blok inicjujący nie zaktualizuje skrótu, co uchroni Cię przed tego rodzaju błędem, ale nadal będziesz narażony na działanie przeciwne - aktualizacja wersji symbolu nie zaktualizuje wersji łańcuchowej:

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}

Dlatego należy zachować ostrożność przy przełączaniu między tymi dwoma kluczowymi formami. Trzymaj się jednego.

szaleństwo
źródło
3

Czy coś podobnego do poniższej pracy?

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

Skopiuje skrót, ale przez większość czasu nie będziesz się tym przejmować. Prawdopodobnie istnieje sposób, aby to zrobić bez kopiowania wszystkich danych.

ChrisInEdmonton
źródło
3

krótszy jednowarstwowy fwiw:

my_hash.inject({}){|h,(k,v)| h.merge({ k.to_sym => v}) }
sensadrom
źródło
2

Co powiesz na to:

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

# my_hash['key'] => "val"
# my_hash[:key]  => "val"
Fredrik Boström
źródło
2

To jest dla osób, które używają mrubyi nie mają symbolize_keyszdefiniowanej żadnej metody:

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

Metoda:

  • symbolizuje tylko klucze, które są String
  • jeśli symbolizowanie łańcucha oznacza utratę niektórych informacji (zastąpienie części skrótu) RuntimeError
  • symbolizują również rekurencyjnie zawarte skróty
  • zwraca symbolizowany skrót
  • działa na miejscu!
Matteo Ragni
źródło
1
Jest literówka w swojej metodzie zapomniałeś !in symbolize_keys. W przeciwnym razie działa dobrze.
Ka Mok
1

Tablica, którą chcemy zmienić.

strings = [„HTML”, „CSS”, „JavaScript”, „Python”, „Ruby”]

Utwórz nową zmienną jako pustą tablicę, abyśmy mogli „.pchować” symbole.

symbole = []

Tutaj definiujemy metodę z blokiem.

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

Koniec kodu.

Jest to więc prawdopodobnie najprostszy sposób na konwersję ciągów znaków na symbole w tablicy (ach) w Rubim. Utwórz tablicę ciągów, a następnie utwórz nową zmienną i ustaw zmienną na pustą tablicę. Następnie wybierz każdy element w pierwszej tablicy utworzonej za pomocą metody „.each”. Następnie użyj kodu blokowego do „.push” wszystkich elementów w nowej tablicy i użyj „.intern lub .to_sym”, aby przekonwertować wszystkie elementy na symbole.

Symbole są szybsze, ponieważ oszczędzają więcej pamięci w kodzie i można ich użyć tylko raz. Symbole są najczęściej używane dla kluczy w skrócie, co jest świetne. Nie jestem najlepszym programistą ruby, ale ta forma kodu bardzo mi pomogła. Jeśli ktoś zna lepszy sposób, proszę udostępnić i możesz użyć tej metody do mieszania!

rubyguest123
źródło
2
Pytanie dotyczyło skrótów, a nie tablic.
Richard_G,
1

Jeśli chcesz rozwiązanie waniliowo-rubinowe, a ponieważ nie mam dostępu do tego ActiveSupportmiejsca, głęboko symbolizuj rozwiązanie (bardzo podobne do poprzednich)

    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
Haris Krajina
źródło
1

Począwszy od wersji Psych 3.0 możesz dodać opcję symbolize_names:

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

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

Uwaga: jeśli masz wersję Psych niższą niż 3.0, symbolize_names:zostanie po cichu zignorowana.

Moje Ubuntu 18.04 zawiera go po wyjęciu z pudełka z ruby ​​2.5.1p57

Rodrigo Estebanez
źródło
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}
Vlad Khomich
źródło
Możesz owinąć anawiasy kwadratowe, aby rozłożyć argument blokowy i uczynić go jeszcze bardziej zwięzłym. Zobacz na przykład moją odpowiedź.
Michael Barton
0

To nie jest dokładnie jedna linijka, ale zamienia wszystkie klucze ciągów w symbole, także te zagnieżdżone:

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
ChristofferJoergensen
źródło
0

Podoba mi się ten liner, gdy nie używam Railsów, ponieważ wtedy nie muszę tworzyć drugiego skrótu i ​​przechowywać dwóch zestawów danych podczas jego przetwarzania:

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 zwraca wartość usuniętego klucza

nevets138
źródło
0

Dobra funkcja Facets 'Hash # deep_rekey jest również dobrą opcją, szczególnie:

  • jeśli w swoim projekcie znajdziesz zastosowanie innego cukru pochodzącego z różnych aspektów,
  • jeśli wolisz czytelność kodu od kryptycznych jedno-liniowych.

Próba:

require 'facets/hash/deep_rekey'
my_hash = YAML.load_file('yml').deep_rekey
Sergikon
źródło
0

W ruby ​​uważam, że jest to najprostszy i najłatwiejszy do zrozumienia sposób na przekształcenie skrótów klawiszowych w skróty na symbole:

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

Dla każdego klucza w skrócie wywołujemy na nim delete, który usuwa go z skrótu (również delete zwraca wartość powiązaną z kluczem, który został usunięty) i natychmiast ustawiamy to na wartość klucza symbolizowanego.


źródło
0

Podobne do poprzednich rozwiązań, ale napisane nieco inaczej.

  • Pozwala to na zagnieżdżenie i / lub tablice.
  • Uzyskaj konwersję kluczy na ciąg jako bonus.
  • Kod nie mutuje hasha, który został przekazany.

    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
jham
źródło