Loại bỏ tất cả các yếu tố trống từ băm / YAML?


133

Làm cách nào để xóa tất cả các thành phần trống (các mục danh sách trống) khỏi tệp Hash hoặc YAML lồng nhau?

Câu trả lời:


70

Bạn có thể thêm một phương thức nhỏ gọn vào Hash như thế này

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

hoặc cho một phiên bản hỗ trợ đệ quy

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

2
nhỏ gọn chỉ nên loại bỏ nils. Không phải giá trị giả
Ismael Abreu

1
Điều này có một vấn đề: Hash#delete_iflà một hoạt động phá hoại, trong khi compactcác phương thức không sửa đổi đối tượng. Bạn có thể sử dụng Hash#reject. Hoặc gọi phương thức Hash#compact!.
tokland

5
Xin lưu ý rằng compactcompact!đạt tiêu chuẩn trong Ruby => 2.4.0 và Rails => 4.1. Họ không đệ quy mặc dù.
Aidan

Phiên bản đệ quy không hoạt động với HashWithIndifferentAccess.. Kiểm tra phiên bản của tôi tại stackoverflow.com/a/53958201/1519240
user1519240

156

Rails 4.1 đã thêm Hash # compactHash # compact! như một phần mở rộng cốt lõi cho Hashlớp của Ruby . Bạn có thể sử dụng chúng như thế này:

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                  
# => {}

Cảnh giác: việc thực hiện này không được đệ quy. Vì tò mò, họ đã triển khai nó bằng cách sử dụng #selectthay vì #delete_iflý do hiệu suất. Xem ở đây cho điểm chuẩn .

Trong trường hợp bạn muốn backport nó vào ứng dụng Rails 3 của bạn:

# 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

3
Đẹp và gọn gàng, nhưng có lẽ đáng chú ý là không giống như câu trả lời được chấp nhận, phần mở rộng Rails không được đệ quy?
SirRawlins

2
Nó bỏ qua băm trống.
Sebastian Palma

142

Sử dụng hsh.delete_if . Trong trường hợp cụ thể của bạn, một cái gì đó như:hsh.delete_if { |k, v| v.empty? }


6
Đệ quy:proc = Proc.new { |k, v| v.kind_of?(Hash) ? (v.delete_if(&l); nil) : v.empty? }; hsh.delete_if(&proc)
Daniel O'Hara

3
Tôi tin rằng có một lỗi đánh máy trong câu trả lời đúng khác của bạn: Proc = Proc.new {| k, v | v.kind_of? (Hash)? (v.delete_if (& Proc); nil): v.empty? }; hsh.delete_if (& Proc)
acw

3
@BSeven có vẻ như họ nghe thấy bạn! api.rubyonrails.org/groupes/Hash.html#method-i-compact (Rails 4.1)
dgilperez

2
Điều này sẽ ném một NoMethodErrornếu vlà con số không.
Jerrod

6
Bạn có thể sử dụng .delete_if {| k, v | v .blank? }
Serhii Nadolynskyi 16/2/2016


7

Điều này cũng sẽ xóa băm trống:

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

1
phiên bản rails, cũng hoạt động với các giá trị của các loại khác ngoài Array, Hash hoặc String (như Fixnum):swoop = Proc.new { |k, v| v.delete_if(&swoop) if v.kind_of?(Hash); v.blank? }
wdspkr

6

Bạn có thể sử dụng từ chối Hash # để xóa các cặp khóa / giá trị trống khỏi Hash 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"}

4
FYI: .empty?ném lỗi cho các số, vì vậy bạn có thể sử dụng .blank?trongRails
ảo ảnh

5

hoạt động cho cả băm và mảng

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

PS dựa trên câu trả lời của ai đó, không thể tìm thấy

sử dụng - Helpers::RecursiveCompact.recursive_compact(something)


4

Tôi biết chủ đề này là một chút cũ nhưng tôi đã đưa ra một giải pháp tốt hơn hỗ trợ băm đa chiều. Nó sử dụng xóa_if? ngoại trừ đa chiều và xóa sạch mọi thứ có giá trị trống theo mặc định và nếu một khối được truyền, nó sẽ được truyền qua các con của nó.

# 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

4

Tôi đã thực hiện một phương thức deep_compact cho phương pháp này để lọc đệ quy các bản ghi nil (và tùy chọn, cả các bản ghi trống):

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

4

Ruby Hash#compact, Hash#compact!Hash#delete_if!không làm việc trên lồng nhau nil, empty?và / hoặc blank?giá trị. Lưu ý rằng trong hai phương pháp thứ hai là phá hoại, và rằng tất cả nil, "", false, []{}giá trị này được tính làblank? .

Hash#compactHash#compact!chỉ khả dụng trong Rails, hoặc phiên bản Ruby 2.4.0 trở lên.

Đây là một giải pháp không phá hủy, loại bỏ tất cả các mảng, băm, chuỗi và nilgiá trị trống, trong khi vẫn giữ tất cả các falsegiá trị:

( blank?có thể được thay thế bằng nil?hoặc empty?khi cần thiết.)

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

Một phiên bản phá hoại:

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

Hoặc, nếu bạn muốn thêm cả hai phiên bản làm phương thức ví dụ trên Hashlớp:

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

Sự lựa chọn khác:

  • Thay thế v.blank? && v != falsebằng v.nil? || v == ""để loại bỏ nghiêm ngặt các chuỗi trống vànil giá trị
  • Thay thế v.blank? && v != falsebằng v.nil?để loại bỏ nghiêm ngặt nilcác giá trị
  • Vân vân.

EDITED 2017/03/15 để giữ falsecác giá trị và trình bày các tùy chọn khác


3

phiên bản của chúng tôi: nó cũng xóa sạch các chuỗi rỗng và các giá trị không

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

3

Trong Simple one liner để xóa các giá trị null trong Hash,

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

cẩn thận, blank?đi cho các chuỗi trống là tốt
Hertzel Guinness

2

Có thể được thực hiện với thư viện facets (một tính năng còn thiếu từ thư viện chuẩn), như thế:

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

Hoạt động với mọi Số lượng (bao gồm Mảng, Hash).

Nhìn cách phương pháp đệ quy được thực hiện.


0

Tôi tin rằng tốt nhất là sử dụng phương pháp tự đệ quy. Đó là cách nó đi sâu như cần thiết. Điều này sẽ xóa cặp giá trị khóa nếu giá trị bằng 0 hoặc Hash trống.

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

Sau đó, sử dụng nó sẽ trông như thế này:

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}}

Để giữ băm trống, bạn có thể đơn giản hóa việc này.

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

hmm tài liệu tham khảo tròn có thể dẫn đến IIUC vòng lặp vô hạn.
Hertzel Guinness

0
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

Lưu ý rằng "khi Hash thì nhỏ gọn (val) .empty?" nên là "khi Hash thì val.compact.empty?"
AlexITC

0

Hãy thử điều này để loại bỏ con số không

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}

hoặc đơn giản hơnhash.compact!
Courtimas

0

Phiên bản đệ quy của https://stackoverflow.com/a/14773555/1519240 hoạt động, nhưng không phải với HashWithIndifferentAccesshoặc các lớp khác là loại Hash ..

Đây là phiên bản tôi đang sử dụng:

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) sẽ chấp nhận nhiều lớp giống như Hash.

Bạn cũng có thể thay thế inject({})bằng inject(HashWithIndifferentAccess.new)nếu bạn muốn truy cập hàm băm mới bằng cả ký hiệu và chuỗi.


0

Đây là thứ tôi có:

# 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

0

Xóa giá trị nil sâu từ một hàm băm.

  # 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

0

Nếu bạn đang sử dụng Rails(hoặc độc lập ActiveSupport), bắt đầu từ phiên bản 6.1, có một compact_blankphương pháp loại bỏ blankcác giá trị khỏi giá trị băm.

Nó sử dụng Object#blank?dưới mui xe để xác định nếu một mục trống.

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

Đây là một liên kết đến các tài liệu và một liên kết đến PR tương đối .

Một biến thể phá hoại cũng có sẵn. Xem Hash#compact_blank!.


Nếu bạn chỉ cần xóa nilcác giá trị,

xin vui lòng, xem xét sử dụng tích hợp Hash#compactHash#compact!phương pháp Ruby .

{ a: 1, b: false, c: nil }.compact
# => { a: 1, b: false }
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.