Как удалить ключ из Hash и получить оставшийся хэш в Ruby / Rails?

404

Чтобы добавить новую пару к Hash, я делаю:

{:a => 1, :b => 2}.merge!({:c => 3})   #=> {:a => 1, :b => 2, :c => 3}

Есть ли аналогичный способ удаления ключа из Hash?

Это работает:

{:a => 1, :b => 2}.reject! { |k| k == :a }   #=> {:b => 2}

но я ожидал бы что-то вроде:

{:a => 1, :b => 2}.delete!(:a)   #=> {:b => 2}

Важно, что возвращаемое значение будет оставшимся хешем, поэтому я мог бы делать такие вещи, как:

foo(my_hash.reject! { |k| k == my_key })

в одной строке.

  • 1
    Вы всегда можете расширить (открыть во время выполнения) встроенный хэш, чтобы добавить этот пользовательский метод, если он вам действительно нужен.
Теги:
hashmap
ruby-on-rails-3
ruby-hash

12 ответов

569
Лучший ответ

Rails имеет исключение/исключение! метод, который возвращает хэш с удаленными ключами. Если вы уже используете Rails, нет смысла создавать собственную версию этого.

class Hash
  # Returns a hash that includes everything but the given keys.
  #   hash = { a: true, b: false, c: nil}
  #   hash.except(:c) # => { a: true, b: false}
  #   hash # => { a: true, b: false, c: nil}
  #
  # This is useful for limiting a set of parameters to everything but a few known toggles:
  #   @person.update(params[:person].except(:admin))
  def except(*keys)
    dup.except!(*keys)
  end

  # Replaces the hash without the given keys.
  #   hash = { a: true, b: false, c: nil}
  #   hash.except!(:c) # => { a: true, b: false}
  #   hash # => { a: true, b: false }
  def except!(*keys)
    keys.each { |key| delete(key) }
    self
  end
end
  • 43
    Вам не нужно использовать полный стек Rails. Вы можете включить ActiveSupport в любое приложение Ruby.
  • 28
    Просто для людей, которым нравятся их откровенные ответы: {a: 1,b: 2}.except!(:a) и если нужно удалить более одного ключа {a: 1,b: 2}.except!(a:, b:)
Показать ещё 3 комментария
152

Oneliner plain ruby, он работает только с ruby > 1.9.x:

1.9.3p0 :002 > h = {:a => 1, :b => 2}
 => {:a=>1, :b=>2} 
1.9.3p0 :003 > h.tap { |hs| hs.delete(:a) }
 => {:b=>2} 

Tap метод всегда возвращает объект, на который вызывается...

В противном случае, если вам требуется active_support/core_ext/hash (что автоматически требуется в каждом приложении Rails), вы можете использовать один из следующих способов в зависимости от ваших потребностей:

➜  ~  irb
1.9.3p125 :001 > require 'active_support/core_ext/hash' => true 
1.9.3p125 :002 > h = {:a => 1, :b => 2, :c => 3}
 => {:a=>1, :b=>2, :c=>3} 
1.9.3p125 :003 > h.except(:a)
 => {:b=>2, :c=>3} 
1.9.3p125 :004 > h.slice(:a)
 => {:a=>1} 

except использует черный список, поэтому он удаляет все ключи, перечисленные в качестве аргументов, а slice использует белый список, поэтому он удаляет все ключи, которые не указаны в качестве аргументов. Также существует версия bang этого метода (except! и slice!), которые изменяют данный хеш, но их возвращаемое значение отличается, оба из них возвращают хэш. Он отображает удаленные ключи для slice! и ключи, которые хранятся для except!:

1.9.3p125 :011 > {:a => 1, :b => 2, :c => 3}.except!(:a)
 => {:b=>2, :c=>3} 
1.9.3p125 :012 > {:a => 1, :b => 2, :c => 3}.slice!(:a)
 => {:b=>2, :c=>3} 
  • 1
    Отличный ответ. Кстати, tap и delete работу в моем ри-1.8.7.
  • 17
    +1 Стоит отметить, что этот метод разрушителен для h . Hash#except как не изменит оригинальный хеш.
Показать ещё 1 комментарий
132

Почему бы просто не использовать:

hash.delete(key)
  • 0
    Я хотел бы сделать foo(h.reject!{ |k| k == :a }) . С твоим предложением мне придется сделать это в две строки.
  • 2
    @dbryson: я согласен, что иногда это того не стоит. Мне просто интересно, почему происходят merge , merge! , delete , но не detele! ...
Показать ещё 6 комментариев
31
#in lib/core_extensions.rb
class Hash
  #pass single or array of keys, which will be removed, returning the remaining hash
  def remove!(*keys)
    keys.each{|key| self.delete(key) }
    self
  end

  #non-destructive version
  def remove(*keys)
    self.dup.remove!(*keys)
  end
end

#in config/initializers/app_environment.rb (or anywhere in config/initializers)
require 'core_extensions'

Я установил это так, чтобы .remove вернул копию хэша с удаленными ключами, а удаляем! изменяет сам хеш. Это соответствует рубиновым соглашениям. например, с консоли

>> hash = {:a => 1, :b => 2}
=> {:b=>2, :a=>1}
>> hash.remove(:a)
=> {:b=>2}
>> hash
=> {:b=>2, :a=>1}
>> hash.remove!(:a)
=> {:b=>2}
>> hash
=> {:b=>2}
>> hash.remove!(:a, :b)
=> {}
29

Существует много способов удалить ключ из хэша и получить оставшийся хэш в Ruby.

  • .slice = > Он вернет выбранные ключи и не удалит их из исходного хеша

    2.2.2 :074 > hash = {"one"=>1, "two"=>2, "three"=>3}
     => {"one"=>1, "two"=>2, "three"=>3} 
    2.2.2 :075 > hash.slice("one","two")
     => {"one"=>1, "two"=>2} 
    2.2.2 :076 > hash
     => {"one"=>1, "two"=>2, "three"=>3} 
    
  • .delete = > Он удалит выбранные ключи из исходного хэша (он может принимать только один ключ и не более одного)

    2.2.2 :094 > hash = {"one"=>1, "two"=>2, "three"=>3}
     => {"one"=>1, "two"=>2, "three"=>3} 
    2.2.2 :095 > hash.delete("one")
     => 1 
    2.2.2 :096 > hash
     => {"two"=>2, "three"=>3} 
    
  • .except = > Он вернет остальные ключи, но ничего не удалит из исходного хэша

    2.2.2 :097 > hash = {"one"=>1, "two"=>2, "three"=>3}
     => {"one"=>1, "two"=>2, "three"=>3} 
    2.2.2 :098 > hash.except("one","two")
     => {"three"=>3} 
    2.2.2 :099 > hash
     => {"one"=>1, "two"=>2, "three"=>3}         
    
  • .delete_if = > Если вам нужно удалить ключ на основе значения. Он, очевидно, удалит соответствующие ключи из исходного хеша

    2.2.2 :115 > hash = {"one"=>1, "two"=>2, "three"=>3, "one_again"=>1}
     => {"one"=>1, "two"=>2, "three"=>3, "one_again"=>1} 
    2.2.2 :116 > value = 1
     => 1 
    2.2.2 :117 > hash.delete_if { |k,v| v == value }
     => {"two"=>2, "three"=>3} 
    2.2.2 :118 > hash
     => {"two"=>2, "three"=>3} 
    

Результаты, основанные на Ruby 2.2.2.

  • 12
    slice и , except добавляются с помощью ActiveSupport::CoreExtensions::Hash . Они не являются частью ядра Ruby. Их можно использовать с помощью require 'active_support/core_ext/hash'
29

Если вы хотите использовать чистый Ruby (no Rails), не хотите создавать методы расширения (возможно, вам нужно это только в одном или двух местах и ​​не хотите загрязнять пространство имен с помощью множества методов) t хотите отредактировать хеш на месте (т.е. вы поклонник функционального программирования, такого как я), вы можете "выбрать":

>> x = {:a => 1, :b => 2, :c => 3}
=> {:a=>1, :b=>2, :c=>3}
>> x.select{|x| x != :a}
=> {:b=>2, :c=>3}
>> x.select{|x| ![:a, :b].include?(x)}
=> {:c=>3}
>> x
=> {:a=>1, :b=>2, :c=>3}
25

Вы можете использовать except! из драгоценного камня facets:

>> require 'facets' # or require 'facets/hash/except'
=> true
>> {:a => 1, :b => 2}.except(:a)
=> {:b=>2}

Исходный хеш не изменяется.

EDIT: как говорит Рассел, грани имеют некоторые скрытые проблемы и не полностью совместимы с API с ActiveSupport. С другой стороны ActiveSupport не так полно, как грани. В конце концов, я бы использовал AS и допустил ошибки в вашем коде.

  • 0
    Просто require 'facets/hash/except' и они не являются «проблемами» (не знаю, какие проблемы у них возникнут, кроме как на 100% AS API). Если вы делаете Rails-проект с использованием AS, имеет смысл, если нет, Facets занимает гораздо меньше места.
  • 0
    @trans ActiveSupport в настоящее время также занимает довольно мало места, и вам могут потребоваться только его части. Точно так же как грани, но с большим количеством глаз на это (таким образом, я предполагаю, что это получает лучшие обзоры).
17

Вместо исправления обезьяны или без необходимости включения больших библиотек вы можете использовать уточнения, если вы используете Ruby 2:

module HashExtensions
  refine Hash do
    def except!(*candidates)
      candidates.each { |candidate| delete(candidate) }
      self
    end

    def except(*candidates)
      dup.remove!(candidates)
    end
  end
end

Вы можете использовать эту функцию, не затрагивая другие части вашей программы, или иметь большие внешние библиотеки.

class FabulousCode
  using HashExtensions

  def incredible_stuff
    delightful_hash.except(:not_fabulous_key)
  end
end
13

в чистом Ruby:

{:a => 1, :b => 2}.tap{|x| x.delete(:a)}   # => {:b=>2}
10

См. Ruby on Rails: удаление нескольких хеш-ключей

hash.delete_if{ |k,| keys_to_delete.include? k }
  • 0
    keys_to_delete.each {| k | hash.delete (k)} намного быстрее для больших наборов данных. поправь меня если не прав.
1

Это однострочный способ сделать это, но он не очень читабельен. Вместо этого рекомендуется использовать две строки.

use_remaining_hash_for_something(Proc.new { hash.delete(:key); hash }.call)
  • 1
    Hash#except и Hash#except! уже упоминалось достаточно. Версия Proc.new не очень читабельна, как вы упомянули, а также более сложна, чем use_remaining_hash_for_something(begin hash.delete(:key); hash end) . Может быть, просто удалите этот ответ.
  • 1
    Сократил мой ответ и удалил то, что уже было сказано. Держу мой ответ вместе с вашим комментарием, потому что они отвечают на вопрос и дают хорошие рекомендации для использования.
-5

Это также сработает: hash[hey] = nil

  • 3
    h = {: a => 1,: b => 2,: c => 3}; ч [: а] = ноль; h.each {| к, v | ставит k} Не совпадает с: h = {: a => 1,: b => 2,: c => 3}; h.delete (а); h.each {| к, v | ставит k}
  • 0
    Удаление ключа из хеша - это не то же самое, что удаление значения ключа из хеша. Так как это может привести людей в замешательство, лучше удалить этот ответ.

Ещё вопросы

Сообщество Overcoder
Наверх
Меню