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

560

Чтобы добавить новую пару в 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
Вы всегда можете расширить (открыть во время выполнения) встроенный хэш, чтобы добавить этот пользовательский метод, если он вам действительно нужен.
dbryson

Ответы:

750

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
Питер Браун
источник
51
Вам не нужно использовать полный стек Rails. Вы можете включить ActiveSupport в любое приложение Ruby.
Фрай
10
Чтобы добавить ответ Фрая, вам даже не нужно загружать весь ActiveSupport; Вы можете просто включить их тогдаrequire "active_support/core_ext/hash/except"
GMA
слишком поздно для редактирования: я имел в виду «включить драгоценные камни», а не «включать их»
GMA
@GMA: когда ваши пять минут редактирования истекли, вы всегда можете скопировать, удалить, изменить и отправить комментарий.
иконоборчество
212

Простой рубин, он работает только с 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} 

кроме того, использует подход черного списка, поэтому он удаляет все ключи, перечисленные как аргументы, в то время как slice использует подход белого списка, поэтому он удаляет все ключи, которые не перечислены в качестве аргументов. Существует также версия взрыва тех методов ( 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} 
Fabio
источник
18
+1 Стоит отметить, что этот метод разрушителен h. Hash#exceptне будет изменять исходный хеш.
Спасибо,
3
Используйте, h.dup.tap { |hs| hs.delete(:a) }чтобы избежать изменения исходного хэша.
Magicode
182

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

hash.delete(key)
dbryson
источник
2
@dbryson: я согласен, что иногда это того не стоит. Я просто удивляюсь , почему есть merge, merge!, delete, но нет detele!...
Миша Moroshko
1
если вам действительно нужно это как один вкладыш, сделайте:foo(hash.delete(key) || hash)
Берт Геталс
13
Это было бы более согласованным с соглашениями Ruby, если бы deleteон не изменял свой параметр, и если бы он delete!существовал и изменял его параметр.
Дэвид Дж
60
Это не возвращает оставшийся хеш, как указано в вопросе, оно возвращает значение, связанное с удаленным ключом.
MhdSyrwan
1
delete возвращает ключ, но также изменяет хеш. Что касается того, почему нет удаления !, я предполагаю, что семантически не имеет смысла вызывать удаление чего-либо и фактически не удалять его. вызов hash.delete () в отличие от hash.delete! () был бы неработоспособен.
eggmatters
85

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

  1. .slice=> Он вернет выбранные ключи и не удалит их из исходного хэша. Используйте, slice!если вы хотите удалить ключи навсегда, иначе используйте простой 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} 
  2. .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} 
  3. .except=> Он вернет оставшиеся ключи, но не удалит ничего из исходного хеша. Используйте, except!если вы хотите удалить ключи навсегда, иначе используйте простой 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}         
  4. .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} 
  5. .compact=> Используется для удаления всех nilзначений из хеша. Используйте, compact!если вы хотите удалить nilзначения навсегда, иначе используйте simple compact.

    2.2.2 :119 > hash = {"one"=>1, "two"=>2, "three"=>3, "nothing"=>nil, "no_value"=>nil}
     => {"one"=>1, "two"=>2, "three"=>3, "nothing"=>nil, "no_value"=>nil} 
    2.2.2 :120 > hash.compact
     => {"one"=>1, "two"=>2, "three"=>3}

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

techdreams
источник
16
sliceи exceptдобавляются с помощью ActiveSupport::CoreExtensions::Hash. Они не являются частью ядра Ruby. Они могут быть использованыrequire 'active_support/core_ext/hash'
Мадис Нымме
3
Начиная с Ruby 2.5 Hash#sliceесть в стандартной библиотеке. ruby-doc.org/core-2.5.0/Hash.html#method-i-slice Yay!
Мадис Нымме
38

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

>> 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}
Юра Тарас
источник
30
#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)
=> {}
Макс Уильямс
источник
26

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

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

Оригинальный хеш не меняется.

РЕДАКТИРОВАТЬ: как говорит Рассел, аспекты имеют некоторые скрытые проблемы и не полностью API-совместимы с ActiveSupport. С другой стороны ActiveSupport не так полон, как аспекты. В конце концов, я бы использовал AS и предоставил крайние случаи в вашем коде.

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

Вместо того, чтобы вносить исправления в обезьяны или без необходимости включать большие библиотеки, вы можете использовать уточнения, если вы используете 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
Мохамад
источник
17

в чистом рубине

{:a => 1, :b => 2}.tap{|x| x.delete(:a)}   # => {:b=>2}
Гамов
источник
13

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

hash.delete_if{ |k,| keys_to_delete.include? k }
Nakilon
источник
keys_to_delete.each {| k | hash.delete (k)} намного быстрее для больших наборов данных. поправь меня если не прав.
Виньеш Джаявель
@VigneshJayavel, вы правы, но ОП хотел вернуть хеш. eachвернул бы массив.
Накилон
3

Было бы здорово, если delete вернул пару удаления хэша. Я делаю это:

hash = {a: 1, b: 2, c: 3}
{b: hash.delete(:b)} # => {:b=>2}
hash  # => {:a=>1, :c=>3} 
frenesim
источник
1

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

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

Несколько способов удалить Key in Hash. Вы можете использовать любой метод снизу

hash = {a: 1, b: 2, c: 3}
hash.except!(:a) # Will remove *a* and return HASH
hash # Output :- {b: 2, c: 3}

hash = {a: 1, b: 2, c: 3}
hash.delete(:a) # will remove *a* and return 1 if *a* not present than return nil

Есть много способов, вы можете посмотреть на Ruby doc of Hash здесь .

Спасибо

Кетан мангукия
источник
-12

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

fdghdfg
источник
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}
obaqueiro
1
Удаление ключа из хеша - это не то же самое, что удаление значения ключа из хеша. Поскольку это может привести людей в замешательство, было бы лучше удалить этот ответ.
Себастьян Пальма