如何从哈希中删除密钥并在Ruby / Rails中获得剩余的哈希?


560

要将新的配对添加到哈希,请执行以下操作:

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

有类似的方法可以从哈希中删除密钥吗?

这有效:

{: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
如果确实需要,您可以始终扩展(在运行时打开)内置的Hash以添加此自定义方法。
dbryson 2011年

Answers:


750

Rails有一个except / except!返回删除了这些键的哈希值的方法。如果您已经在使用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堆栈。您可以在任何Ruby应用程序中包括ActiveSupport。
Fryie 2013年

10
要添加到Fryie的答案中,您甚至不需要加载所有ActiveSupport。您可以将它们包括在内require "active_support/core_ext/hash/except"
-GMA

来不及编辑:我的意思是“包括宝石”而不是“包括它们”
GMA

@GMA:编辑五分钟后,您随时可以复制,删除,修改和重新发布评论。
iconoclast

211

Oneliner纯红宝石,仅适用于> 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} 

除了使用黑名单方法,因此它将删除所有列为args的键,而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} 

18
+1值得一提的是,此方法具有破坏性hHash#except不会修改原始哈希。
谢谢您

3
使用h.dup.tap { |hs| hs.delete(:a) }以避免修改原来的哈希值。
Magicode

181

为什么不使用:

hash.delete(key)

2
@dbryson:我同意有时候这是不值得的。我只是想知道为什么有mergemerge!delete,但没有detele!...
米莎Moroshko

1
如果您确实需要将它作为一个衬板,请执行以下操作:foo(hash.delete(key) || hash)
Bert Goethals,

13
这将是用Ruby惯例更加一致,如果delete没有修改其参数,如果delete!存在,没有修改其参数。
David J.

60
这不会返回问题中提到的其余哈希,而是将返回与已删除键相关联的值。
MhdSyrwan

1
delete返回键,但它也会更改哈希值。至于为什么没有删除!,我的猜测是,从语义上讲,对某事调用delete而不是实际上将其删除是没有意义的。调用hash.delete()而不是hash.delete!()将是一个空操作。
eggmatters 2015年

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值永久使用其他简单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的结果。


15
sliceexcept使用添加ActiveSupport::CoreExtensions::Hash。它们不是Ruby核心的一部分。他们可以通过使用require 'active_support/core_ext/hash'
马迪斯Nõmme

3
由于Ruby 2.5 Hash#slice在标准库中。ruby-doc.org/core-2.5.0/Hash.html#method-i-slice耶!
MadisNõmme19年

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!facetsgem 使用:

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

原始哈希不会更改。

编辑:正如Russel所说,方面存在一些隐藏的问题,并且与ActiveSupport并不完全兼容API。另一方面,ActiveSupport不如构面完整。最后,我将使用AS并在代码中使用边缘情况。


Just require 'facets/hash/except'和它们都不是“问题”(不确定不是100%AS API还是会出现什么问题)。如果您正在使用AS进行Rails项目,那么Facets的占用空间要小得多。
2016年

如今,@ 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

在纯Ruby中:

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


3

如果delete返回哈希的delete对,那就太好了。我正在这样做:

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

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)。也许只是删除此答案。
Michael Kohl 2014年

1
缩短了我的回答,删除了已经说过的话。我的回答与您的评论保持一致,因为他们回答了问题并提出了很好的使用建议。
the_minted 2014年

0

多种删除哈希键的方法。您可以使用下面的任何方法

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

有很多方法,您可以在此处查看Hash的Ruby文档。

谢谢


-12

这也将起作用: hash[hey] = nil


3
h = {:a => 1,:b => 2,:c => 3}; h [:a] = nil; h.each {| k,v | puts k}与以下内容不同:h = {:a => 1,:b => 2,:c => 3}; h.delete(:a); h.each {| k,v | 放入k}
obaqueiro 2014年

1
从哈希中删除键与从哈希中删除键的值不同。由于这可能会使人们感到困惑,因此最好删除此答案。
塞巴斯蒂安·帕尔玛
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.