【问题标题】:Best way to convert strings to symbols in hash将字符串转换为哈希符号的最佳方法
【发布时间】:2010-10-22 10:57:10
【问题描述】:

在 Ruby 中将哈希中的所有键从字符串转换为符号的(最快/最干净/直接)方法是什么?

这在解析 YAML 时会很方便。

my_hash = YAML.load_file('yml')

我希望能够使用:

my_hash[:key] 

而不是:

my_hash['key']

【问题讨论】:

  • dup?
  • 如果您使用的是 Rails,hash.symbolize_keyshash.deep_symbolize_keys 可以完成这项工作。
  • 乔希如果您将您的评论放入答案中,我会投票给您。 require 'rails';hash.deep_symbolize_keys 在 irb 或 pry 中效果很好。 :D

标签: ruby hashmap


【解决方案1】:

Ruby >= 2.5 (docs) 中,您可以使用:

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 

【讨论】:

  • 啊,很抱歉不清楚 - inject 不会修改调用者。你需要做 my_hash = my_hash.inject({}){|memo,(k,v)|备忘录[k.to_sym] = v;备忘录}
  • 这正是我想要的。我对其进行了一些修改并添加了一些行,甚至可以在嵌套的哈希中创建符号。如果您有兴趣,请看这里:any-where.de/blog/ruby-hash-convert-string-keys-to-symbols
  • 在 Ruby 1.9 中,您可以像这样使用each_with_objectmy_hash.each_with_object({}){|(k,v), h| h[k.to_sym] = v}
  • 这不处理递归哈希...查找一次性但不是 DRY。
  • @BryanM。我很晚才开始讨论这个问题:-) 但您也可以使用.tap 方法来消除最后传递memo 的需要。我已经创建了所有解决方案的清理版本(以及递归的)gist.github.com/Integralist/9503099
【解决方案2】:

在 Rails 中你可以使用:

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

转换为:

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

【讨论】:

  • deep_symbolize_keys 被添加到 Rails 的 Hash 扩展中,但它不是 Ruby 核心的一部分。
【解决方案3】:

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 包含开箱即用的 ruby​​ 2.5.1p57

【讨论】:

    【解决方案4】:

    与以前的解决方案类似,但编写方式略有不同。

    • 这允许嵌套和/或具有数组的哈希。
    • 将键转换为字符串作为奖励。
    • 代码不会改变传入的哈希值。

      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
      

    【讨论】:

      【解决方案5】:

      如果您使用的是 json,并且想将其用作哈希,那么在核心 Ruby 中您可以这样做:

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

      symbolize_names:如果设置为 true,则返回 JSON 对象中名称(键)的符号。否则返回字符串。字符串是默认值。

      文档:Json#parse symbolize_names

      【讨论】:

      • symbol_hash = JSON.parse(JSON.generate(YAML.safe_load(FILENAME)), symbolize_names: true) 是一种非常干燥(但效率低下)的方法,如果来自 YAML 文件,则可以快速获取带有嵌套键作为符号的哈希。
      【解决方案6】:

      由于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_keyssymbolize_keys 那样修改值的等价物。
      • 有没有办法对键进行深度符号化
      • 这应该是2018年后选择的方案。
      【解决方案7】:

      这适用于使用 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 的键
      • 如果符号化一个字符串意味着丢失一些信息(覆盖部分哈希)引发RuntimeError
      • 符号化也递归包含哈希
      • 返回符号化哈希
      • 工作到位!

      【讨论】:

      • 您的方法中有一个错字,您忘记了symbolize_keys 中的!。否则工作正常。
      【解决方案8】:

      在 ruby​​ 中,我发现这是将哈希中的字符串键转换为符号的最简单易懂的方法:

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

      对于散列中的每个键,我们在其上调用 delete 将其从散列中删除(也 delete 返回与被删除的键关联的值),我们立即将其设置为符号化键。

      【讨论】:

        【解决方案9】:

        如果您需要这样做的原因是因为您的数据最初来自 JSON,您可以通过在摄取 JSON 时传入 :symbolize_names 选项来跳过任何此解析。 p>

        无需 Rails,可与 Ruby >1.9 一起使用

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

        【讨论】:

          【解决方案10】:

          Facets' Hash#deep_rekey 也是一个不错的选择,尤其是:

          • 如果您发现项目中的其他糖分方面有用,
          • 如果您更喜欢代码可读性而不是神秘的单行代码。

          示例:

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

          【讨论】:

            【解决方案11】:

            这是我的嵌套散列的唯一衬里

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

            【讨论】:

            • 仅供参考,仅适用于 Rails。在这种情况下,HashWithIndifferentAccess 可能是更好的选择。
            【解决方案12】:

            这里有这么多答案,但一种方法 rails 函数是hash.symbolize_keys

            【讨论】:

            【解决方案13】:

            symbolize_keys 递归地用于任何哈希:

            class Hash
              def symbolize_keys
                self.is_a?(Hash) ? Hash[ self.map { |k,v| [k.respond_to?(:to_sym) ? k.to_sym : k, v.is_a?(Hash) ? v.symbolize_keys : v] } ] : self
              end
            end
            

            【讨论】:

              【解决方案14】:

              如果您使用的是 Rails,则要简单得多 - 您可以使用 HashWithIndifferentAccess 并以字符串和符号的形式访问键:

              my_hash.with_indifferent_access 
              

              另见:

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


              或者您可以使用很棒的“Ruby Facets”Gem,它包含许多对 Ruby 核心和标准库类的扩展。

                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

              【讨论】:

              • 实际上恰恰相反。它将符号转换为字符串。要转换为符号,请使用 my_hash.symbolize_keys
              • #symbolize_keys 仅适用于 Rails - 不适用于普通的 Ruby / irb。另请注意,#symbolize_keys 不适用于深度嵌套的哈希。
              【解决方案15】:

              如果您想要原版红宝石解决方案,而我无法访问 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
              

              【讨论】:

                【解决方案16】:

                我们要改变的数组。

                字符串 = [“HTML”、“CSS”、“JavaScript”、“Python”、“Ruby”]

                创建一个新变量作为空数组,以便我们可以“.push”其中的符号。

                符号 = [ ]

                这里是我们用块定义方法的地方。

                strings.each {|x|符号.push(x.intern)}

                代码结束。

                因此,这可能是在 Ruby 中将字符串转换为数组中符号的最直接方法。创建一个字符串数组,然后创建一个新变量并将该变量设置为一个空数组。然后选择您使用“.each”方法创建的第一个数组中的每个元素。然后使用块代码“.push”新数组中的所有元素,并使用“.intern 或 .to_sym”将所有元素转换为符号。

                符号速度更快,因为它们可以在您的代码中节省更多内存,并且您只能使用它们一次。符号最常用于哈希中的键,这很棒。我不是最好的 ruby​​ 程序员,但这种形式的代码对我帮助很大。如果有人知道更好的方法,请分享,你也可以使用这种方法进行哈希!

                【讨论】:

                • 问题是关于哈希,而不是数组。
                【解决方案17】:

                当我不使用 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 返回被删除键的值

                【讨论】:

                  【解决方案18】:

                  这不完全是单行,但它将所有字符串键转换为符号,还有嵌套的:

                  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
                  

                  【讨论】:

                    【解决方案19】:

                    http://api.rubyonrails.org/classes/Hash.html#method-i-symbolize_keys

                    hash = { 'name' => 'Rob', 'age' => '28' }
                    hash.symbolize_keys
                    # => { name: "Rob", age: "28" }
                    

                    【讨论】:

                      【解决方案20】:

                      一个更短的单行fwiw:

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

                      【讨论】:

                        【解决方案21】:

                        对于 Ruby 中 YAML 的特定情况,如果键以“:”开头,它们将被自动作为符号进行实习。

                        需要'yaml' 需要'pp' yaml_str = " 连接: - 主机:host1.example.com 端口:10000 - 主机:host2.example.com 端口:20000 " yaml_sym = " :连接: - :主机:host1.example.com :端口:10000 - :主机:host2.example.com :端口:20000 " 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 {“连接”=> [{"port"=>10000, "host"=>"host1.example.com"}, {"port"=>20000, "host"=>"host2.example.com"}]} 细绳 {:连接=> [{:port=>10000, :host=>"host1.example.com"}, {:port=>20000, :host=>"host2.example.com"}]} 象征

                        【讨论】:

                        • 甜蜜!有没有办法将YAML#load_file 设置为默认所有键为符号而不是字符串,而不必以冒号开头每个键?
                        【解决方案22】:

                        params.symbolize_keys 也可以。该方法将哈希键转换为符号并返回一个新的哈希。

                        【讨论】:

                        • 该方法不是核心 Ruby。这是一个 Rails 方法。
                        【解决方案23】:

                        对@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
                        

                        【讨论】:

                        • 如果您包括为什么要修改对象以供人们浏览答案会很有帮助。
                        【解决方案24】:

                        这是一种深度符号化对象的方法

                        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 ,我也会使用这个 :)
                        【解决方案25】:

                        如果您使用的是 Rails,这里有一个更好的方法:

                        params.symbolize_keys

                        结束。

                        如果你不是,就撕掉他们的代码(它也在链接中):

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

                        【讨论】:

                        • to_optionssybolize_keys 的别名。
                        • 不会符号化嵌套哈希。
                        • 我将链接切换到 symbolize_keys 并使用新的和工作的 (Rails 3) URL。我最初只是修复了to_options 的 URL,但该链接的文档为零。 symbolize_keys 实际上有一个描述,所以我用它来代替。
                        • deep_symbolize_keys!。在轨道上工作 2+
                        • 对于那些好奇如何反其道而行之的人,hash.stringify_keys 有效。
                        【解决方案26】:

                        更简洁:

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

                        【讨论】:

                        • 这似乎是显而易见的选择。
                        • 它不代表嵌套哈希。
                        • 更易读的版本:my_hash.map { |k, v| [k.to_sym, v] }.to_h
                        【解决方案27】:
                        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 括在括号中以分解块参数以使其更加简洁。例如,请参阅我的答案。
                        【解决方案28】:

                        这个怎么样:

                        my_hash = HashWithIndifferentAccess.new(YAML.load_file('yml'))
                        
                        # my_hash['key'] => "val"
                        # my_hash[:key]  => "val"
                        

                        【讨论】:

                          【解决方案29】:

                          我真的很喜欢 Mash gem。

                          你可以mash['key'],或mash[:key],或mash.key

                          【讨论】:

                          【解决方案30】:

                          你可以偷懒,把它包在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}
                          

                          您也可以让 init 块不更新哈希,这样可以保护您免受此类错误的影响,但您仍然容易受到相反的影响 - 更新符号版本不会更新字符串版本:

                          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}
                          

                          所以要注意这些是在两种关键形式之间切换。坚持一个。

                          【讨论】:

                            猜你喜欢
                            • 1970-01-01
                            • 2014-06-21
                            • 2016-10-21
                            • 1970-01-01
                            • 2010-10-10
                            • 2020-10-31
                            • 1970-01-01
                            • 1970-01-01
                            相关资源
                            最近更新 更多