Видалення всіх порожніх елементів з хешу / YAML?


Відповіді:


70

Ви можете додати компактний метод до Хеша, як це

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

2
компактні повинні видаляти лише нулі. Не фальшиві значення
Ісмаель Абреу

1
У цьому є проблема: Hash#delete_ifце руйнівна операція, тоді як compactметоди не змінюють об'єкт. Можна використовувати Hash#reject. Або викликати метод Hash#compact!.
tokland

5
Зверніть увагу, що compactі compact!стандартний у Ruby => 2.4.0 та Rails => 4.1. Хоча вони не рекурсивні.
помічник

Рекурсивна версія не працює з HashWithIndifferentAccess.. Перевірте свою версію на stackoverflow.com/a/53958201/1519240
user1519240

157

До рейки 4.1 додано хеш # компактний і хеш # компактний! як основне розширення до 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міркувань продуктивності. Ознайомтесь тут із еталоном .

У випадку, якщо ви хочете створити його назад у своєму додатку 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

3
Приємно і охайно, але, напевно, варто відзначити, що на відміну від прийнятої відповіді розширення Rails не є рекурсивним?
SirRawlins

2
Він опускає порожні хеші.
Себастьян Пальма

142

Використовуйте hsh.delete_if . У вашому конкретному випадку щось на кшталт:hsh.delete_if { |k, v| v.empty? }


6
Рекурсивний:proc = Proc.new { |k, v| v.kind_of?(Hash) ? (v.delete_if(&l); nil) : v.empty? }; hsh.delete_if(&proc)
Даніель О'Хара

3
Я вважаю, що у вашій інакше правильній відповіді є помилка друку: proc = Proc.new {| k, v | v.kind_of? (хеш)? (v.delete_if (& proc); nil): v.empty? }; hsh.delete_if (& proc)
acw

3
@BSeven, здається, вони тебе чули! api.rubyonrails.org/classes/Hash.html#method-i-compact (Rails 4.1)
dgilperez

2
Це кине а, NoMethodErrorякщо vнуль.
Джеррод

6
Ви можете використовувати .delete_if {| k, v | v.blank? }
Сергій Надолинський


7

Цей також видалить порожні хеші:

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

1
версія rails, яка також працює зі значеннями інших типів, ніж Array, Hash або String (як Fixnum):swoop = Proc.new { |k, v| v.delete_if(&swoop) if v.kind_of?(Hash); v.blank? }
wdspkr

6

Ви можете використовувати Hash # відхилити для видалення порожніх пар ключів / значень з рубіну Hash.

# 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?кидає помилку для чисел, тому ви можете використовувати .blank?вRails
ілюзіоніст

5

працює як для хешей, так і для масивів

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 на основі відповіді когось, не можу знайти

використання - Helpers::RecursiveCompact.recursive_compact(something)


4

Я знаю, що цей потік трохи старий, але я придумав краще рішення, яке підтримує багатовимірні хеші. Він використовує 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

4

Для цього я створив метод deep_compact, який рекурсивно фільтрує нульові записи (і, необов'язково, порожні записи):

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

Рубі Hash#compact , Hash#compact!і Hash#delete_if!не працюють на вкладеній nil, empty?і / або blank?значення. Зверніть увагу , що останні два методу є руйнівними, і що все nil, "", false, []і {}значення, вважаютьсяblank? .

Hash#compact і Hash#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/03/15, щоб зберегти falseзначення та представити інші варіанти


3

наша версія: вона також очищає порожні рядки та нульові значення

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

У простому одному вкладиші для видалення нульових значень у Hash,

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

обережно, blank?йдеться і про порожні рядки
Герцель Гіннес

2

Це можна зробити за допомогою бібліотеки граней (відсутні функції в стандартній бібліотеці), наприклад:

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

Працює з будь-якими численними (включаючи Array, Hash).

Подивіться, як реалізується рекурсивно метод .


0

Я вважаю, що найкраще використовувати метод саморекурсії. Таким чином він іде так глибоко, як потрібно. Це видалить пару значень ключа, якщо значення дорівнює нулю або порожній хеш.

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.
Герцель Гіннес

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

Зауважте, що "коли Hash тоді компактний (val). Порожній?" має бути "коли Hash тоді val.compact.empty?"
AlexITC

0

Спробуйте це зняти нуль

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!
courtimas

0

Рекурсивна версія 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) прийме більше класів, схожих на хеш.

Ви також можете замінити inject({}), inject(HashWithIndifferentAccess.new)якщо ви хочете отримати доступ до нового хешу, використовуючи як символ, так і рядок.


0

Ось що я маю:

# 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

Глибоке видалення нульових значень з хеша.

  # 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

Якщо ви використовуєте 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 }

Ось посилання на документи та посилання на відносний PR .

Також є деструктивний варіант. Див Hash#compact_blank!.


Якщо потрібно видалити тільки nil значення,

будь ласка, подумайте про використання вбудованих методів Hash#compactі Hash#compact!методів Ruby .

{ a: 1, b: false, c: nil }.compact
# => { a: 1, b: false }
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.