【问题标题】:Removing all empty elements from a hash / YAML?从哈希/ YAML 中删除所有空元素?
【发布时间】:2011-03-27 21:51:17
【问题描述】:

如何从嵌套的 Hash 或 YAML 文件中删除所有空元素(空列表项)?

【问题讨论】:

    标签: ruby-on-rails ruby hash yaml


    【解决方案1】:

    Rails 4.1 添加了 Hash#compactHash#compact! 作为 Ruby 的 Hash 类的核心扩展。你可以像这样使用它们:

    hash = { a: true, b: false, c: nil }
    hash.compact                        
    # => { a: true, b: false }
    hash                                
    # => { a: true, b: false, c: nil }
    hash.compact!                        
    # => { a: true, b: false }
    hash                                
    # => { a: true, b: false }
    { c: nil }.compact                  
    # => {}
    

    注意:这个实现不是递归的。出于好奇,出于性能原因,他们使用#select 而不是#delete_if 来实现它。见here for the benchmark

    如果您想将其反向移植到 Rails 3 应用程序:

    # config/initializers/rails4_backports.rb
    
    class Hash
      # as implemented in Rails 4
      # File activesupport/lib/active_support/core_ext/hash/compact.rb, line 8
      def compact
        self.select { |_, value| !value.nil? }
      end
    end
    

    【讨论】:

    • 漂亮整洁,但可能值得注意的是,与公认的答案不同,Rails 扩展不是递归的?
    • 它忽略了空的哈希值。
    【解决方案2】:

    使用hsh.delete_if。在您的具体情况下,例如:hsh.delete_if { |k, v| v.empty? }

    【讨论】:

    • 递归一:proc = Proc.new { |k, v| v.kind_of?(Hash) ? (v.delete_if(&l); nil) : v.empty? }; hsh.delete_if(&proc)
    • 我相信您的正确答案中有错字:proc = Proc.new { |k, v| v.kind_of?(Hash) ? (v.delete_if(&proc); nil) : v.empty? }; hsh.delete_if(&proc)
    • @BSeven 他们似乎听到了你的声音! api.rubyonrails.org/classes/Hash.html#method-i-compact (Rails 4.1)
    • 如果v 为nil,这将抛出NoMethodError
    • 你可以使用 .delete_if { |k, v| v.空白? }
    【解决方案3】:

    你可以像这样向 Hash 添加一个紧凑的方法

    class Hash
      def compact
        delete_if { |k, v| v.nil? }
      end
    end
    

    或支持递归的版本

    class Hash
      def compact(opts={})
        inject({}) do |new_hash, (k,v)|
          if !v.nil?
            new_hash[k] = opts[:recurse] && v.class == Hash ? v.compact(opts) : v
          end
          new_hash
        end
      end
    end
    

    【讨论】:

    • compact 应该只删除 nil。非虚假值
    • 这里有一个问题:Hash#delete_if 是破坏性操作,而compact 方法不会修改对象。您可以使用Hash#reject。或者调用方法Hash#compact!
    • 请注意 compactcompact! 在 Ruby => 2.4.0 和 Rails => 4.1 中是标准的。但它们是非递归的。
    • 递归版本不适用于HashWithIndifferentAccess.. 在stackoverflow.com/a/53958201/1519240查看我的版本
    • 查看我的基于标准compacttransform_values的递归实现!
    【解决方案4】:

    compact_blank (Rails 6.1+)

    如果您使用Rails(或独立的ActiveSupport),从6.1 版本开始,有一个compact_blank 方法可以从哈希中删除blank 值。

    它在底层使用Object#blank? 来确定项目是否为空白。

    { a: "", b: 1, c: nil, d: [], e: false, f: true }.compact_blank
    # => { b: 1, f: true }
    

    这是link to the docslink to the relative PR

    还提供破坏性变体。见Hash#compact_blank!


    如果您只需要删除 nil 值,

    请考虑使用 Ruby 内置的 Hash#compactHash#compact! 方法。

    { a: 1, b: false, c: nil }.compact
    # => { a: 1, b: false }
    

    【讨论】:

      【解决方案5】:

      如果您使用的是 Ruby 2.4+,您可以致电 compactcompact!

      h = { a: 1, b: false, c: nil }
      h.compact! #=> { a: 1, b: false }
      

      https://ruby-doc.org/core-2.4.0/Hash.html#method-i-compact-21

      【讨论】:

      【解决方案6】:

      这个也会删除空哈希:

      swoop = Proc.new { |k, v| v.delete_if(&swoop) if v.kind_of?(Hash);  v.empty? }
      hsh.delete_if &swoop
      

      【讨论】:

      • rails 版本,也适用于数组、哈希或字符串以外的其他类型的值(如 Fixnum):swoop = Proc.new { |k, v| v.delete_if(&swoop) if v.kind_of?(Hash); v.blank? }
      【解决方案7】:

      您可以使用 Hash#reject 从 ruby​​ 哈希中删除空键/值对。

      # Remove empty strings
      { a: 'first', b: '', c: 'third' }.reject { |key,value| value.empty? } 
      #=> {:a=>"first", :c=>"third"}
      
      # Remove nil
      {a: 'first', b: nil, c: 'third'}.reject { |k,v| v.nil? } 
      # => {:a=>"first", :c=>"third"}
      
      # Remove nil & empty strings
      {a: '', b: nil, c: 'third'}.reject { |k,v| v.nil? || v.empty? } 
      # => {:c=>"third"}
      

      【讨论】:

      • 仅供参考:.empty? 会抛出数字错误,因此您可以在 Rails 中使用 .blank?
      【解决方案8】:

      适用于哈希和数组

      module Helpers
        module RecursiveCompact
          extend self
      
          def recursive_compact(hash_or_array)
            p = proc do |*args|
              v = args.last
              v.delete_if(&p) if v.respond_to? :delete_if
              v.nil? || v.respond_to?(:"empty?") && v.empty?
            end
      
            hash_or_array.delete_if(&p)
          end
        end
      end
      

      附:根据某人的回答,找不到

      用法 - Helpers::RecursiveCompact.recursive_compact(something)

      【讨论】:

        【解决方案9】:

        我知道这个线程有点旧,但我想出了一个更好的解决方案,它支持多维散列。它使用delete_if?除了它的多维之外,默认情况下会清除任何具有空值的内容,如果传递了一个块,则它会通过它的子级传递下去。

        # Hash cleaner
        class Hash
            def clean!
                self.delete_if do |key, val|
                    if block_given?
                        yield(key,val)
                    else
                        # Prepeare the tests
                        test1 = val.nil?
                        test2 = val === 0
                        test3 = val === false
                        test4 = val.empty? if val.respond_to?('empty?')
                        test5 = val.strip.empty? if val.is_a?(String) && val.respond_to?('empty?')
        
                        # Were any of the tests true
                        test1 || test2 || test3 || test4 || test5
                    end
                end
        
                self.each do |key, val|
                    if self[key].is_a?(Hash) && self[key].respond_to?('clean!')
                        if block_given?
                            self[key] = self[key].clean!(&Proc.new)
                        else
                            self[key] = self[key].clean!
                        end
                    end
                end
        
                return self
            end
        end
        

        【讨论】:

          【解决方案10】:

          我为此创建了一个 deep_compact 方法,它递归地过滤掉 nil 记录(以及可选的空白记录):

          class Hash
            # Recursively filters out nil (or blank - e.g. "" if exclude_blank: true is passed as an option) records from a Hash
            def deep_compact(options = {})
              inject({}) do |new_hash, (k,v)|
                result = options[:exclude_blank] ? v.blank? : v.nil?
                if !result
                  new_value = v.is_a?(Hash) ? v.deep_compact(options).presence : v
                  new_hash[k] = new_value if new_value
                end
                new_hash
              end
            end
          end
          

          【讨论】:

            【解决方案11】:

            Ruby 的 Hash#compactHash#compact!Hash#delete_if! 不适用于嵌套的 nilempty? 和/或 blank? 值。请注意,后两种方法是破坏性的,所有nil""false[]{} 值都计为blank?

            Hash#compactHash#compact! 仅在 Rails 或 Ruby 2.4.0 及更高版本中可用。

            这是一个非破坏性解决方案,它删除所有空数组、哈希、字符串和nil 值,同时保留所有false 值:

            blank? 可以根据需要替换为nil?empty?。)

            def remove_blank_values(hash)
              hash.each_with_object({}) do |(k, v), new_hash|
                unless v.blank? && v != false
                  v.is_a?(Hash) ? new_hash[k] = remove_blank_values(v) : new_hash[k] = v
                end
              end
            end
            

            破坏性版本:

            def remove_blank_values!(hash)
              hash.each do |k, v|
                if v.blank? && v != false
                  hash.delete(k)
                elsif v.is_a?(Hash)
                  hash[k] = remove_blank_values!(v)
                end
              end
            end
            

            或者,如果您想将两个版本都添加为 Hash 类的实例方法:

            class Hash
              def remove_blank_values
                self.each_with_object({}) do |(k, v), new_hash|
                  unless v.blank? && v != false
                    v.is_a?(Hash) ? new_hash[k] = v.remove_blank_values : new_hash[k] = v
                  end
                end
              end
            
              def remove_blank_values!
                self.each_pair do |k, v|
                  if v.blank? && v != false
                    self.delete(k)
                  elsif v.is_a?(Hash)
                    v.remove_blank_values!
                  end
                end
              end
            end
            

            其他选项:

            • v.blank? && v != false 替换为v.nil? || v == "" 以严格删除空字符串和nil
            • v.blank? && v != false 替换为v.nil? 以严格删除nil

            于 2017 年 3 月 15 日编辑以保留 false 值并提供其他选项

            【讨论】:

              【解决方案12】:

              我们的版本: 它还清除空字符串和 nil 值

              class Hash
              
                def compact
                  delete_if{|k, v|
              
                    (v.is_a?(Hash) and v.respond_to?('empty?') and v.compact.empty?) or
                        (v.nil?)  or
                        (v.is_a?(String) and v.empty?)
                  }
                end
              
              end
              

              【讨论】:

                【解决方案13】:

                在用于删除 Hash 中的空值的 Simple one liner 中,

                rec_hash.each {|key,value| rec_hash.delete(key) if value.blank? } 
                

                【讨论】:

                • 小心,blank? 也适用于空字符串
                【解决方案14】:

                可以使用facets 库(标准库中缺少的功能)来完成,如下所示:

                require 'hash/compact'
                require 'enumerable/recursively'
                hash.recursively { |v| v.compact! }
                

                适用于任何 Enumerable(包括数组、哈希)。

                看看recursively method是如何实现的。

                【讨论】:

                  【解决方案15】:

                  https://stackoverflow.com/a/14773555/1519240 的递归版本有效,但不适用于 HashWithIndifferentAccess 或其他类似 Hash 的类。

                  这是我正在使用的版本:

                  def recursive_compact
                    inject({}) do |new_hash, (k,v)|
                      if !v.nil?
                        new_hash[k] = v.kind_of?(Hash) ? v.recursive_compact : v
                      end
                      new_hash
                    end
                  end
                  

                  kind_of?(Hash) 将接受更多类似于 Hash 的类。

                  如果您想同时使用符号和字符串访问新的哈希值,也可以将 inject({}) 替换为 inject(HashWithIndifferentAccess.new)

                  【讨论】:

                    【解决方案16】:

                    我相信最好使用自递归方法。这样,它就可以根据需要深入。如果值为 nil 或空 Hash,这将删除键值对。

                    class Hash
                      def compact
                        delete_if {|k,v| v.is_a?(Hash) ? v.compact.empty? : v.nil? }
                      end
                    end
                    

                    然后使用它会是这样的:

                    x = {:a=>{:b=>2, :c=>3}, :d=>nil, :e=>{:f=>nil}, :g=>{}}
                    # => {:a=>{:b=>2, :c=>3}, :d=>nil, :e=>{:f=>nil}, :g=>{}} 
                    x.compact
                    # => {:a=>{:b=>2, :c=>3}}
                    

                    要保留空哈希,您可以将其简化为。

                    class Hash
                      def compact
                        delete_if {|k,v| v.compact if v.is_a?(Hash); v.nil? }
                      end
                    end
                    

                    【讨论】:

                    • 嗯。循环引用可能导致无限循环 IIUC。
                    【解决方案17】:
                    class Hash   
                      def compact
                        def _empty?(val)
                          case val
                          when Hash     then val.compact.empty?
                          when Array    then val.all? { |v| _empty?(v) }
                          when String   then val.empty?
                          when NilClass then true
                          # ... custom checking 
                          end
                        end
                    
                        delete_if { |_key, val| _empty?(val) }   
                      end 
                    end
                    

                    【讨论】:

                    • 注意“当 Hash 然后 compact(val).empty?”应该是“当 Hash 然后 val.compact.empty?”
                    【解决方案18】:

                    试试这个来删除 nil

                    hash = { a: true, b: false, c: nil }
                    => {:a=>true, :b=>false, :c=>nil}
                    hash.inject({}){|c, (k, v)| c[k] = v unless v.nil?; c}
                    => {:a=>true, :b=>false}
                    

                    【讨论】:

                    • 或者干脆hash.compact!
                    【解决方案19】:

                    这是我有的东西:

                    # recursively remove empty keys (hashes), values (array), hashes and arrays from hash or array
                    def sanitize data
                      case data
                      when Array
                        data.delete_if { |value| res = sanitize(value); res.blank? }
                      when Hash
                        data.delete_if { |_, value| res = sanitize(value); res.blank? }
                      end
                      data.blank? ? nil : data
                    end
                    

                    【讨论】:

                      【解决方案20】:

                      从哈希中深度删除 nil 值。

                        # returns new instance of hash with deleted nil values
                        def self.deep_remove_nil_values(hash)
                          hash.each_with_object({}) do |(k, v), new_hash|
                            new_hash[k] = deep_remove_nil_values(v) if v.is_a?(Hash)
                            new_hash[k] = v unless v.nil?
                          end
                        end
                      
                        # rewrite current hash
                        def self.deep_remove_nil_values!(hash)
                          hash.each do |k, v|
                            deep_remove_nil_values(v) if v.is_a?(Hash)
                            hash.delete(k) if v.nil?
                          end
                        end
                      

                      【讨论】:

                        【解决方案21】:

                        在 ruby​​ 2.7 中有标准的 compacttransform_values 方法,在 Hash 上,所以你可以这样做:

                        class Hash 
                          def deep_compact
                            compact.transform_values{|vl| vl.is_a?(Hash) ? vl.deep_compact : vl }
                          end
                        end
                        

                        这是最简洁的实现,恕我直言。

                        【讨论】:

                          猜你喜欢
                          • 2011-08-23
                          • 1970-01-01
                          • 2011-12-13
                          • 2013-11-09
                          • 2016-01-02
                          • 1970-01-01
                          • 1970-01-01
                          • 1970-01-01
                          • 1970-01-01
                          相关资源
                          最近更新 更多