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

How to remove a key from Hash and get the remaining hash in Ruby/Rails?

提问人:Misha Moroshko 提问时间:6/3/2011 最后编辑:Shannon Scott SchupbachMisha Moroshko 更新时间:7/21/2023 访问量:633995

问:

要向 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 })

在一行中。

Ruby-on-Rails Ruby-on-Rails-3 哈希图 Ruby-hash

评论

1赞 dbryson 6/3/2011
如果您确实需要,您可以随时扩展(在运行时打开)内置的 Hash 以添加此自定义方法。
0赞 B Seven 10/24/2020
Ruby 3 将拥有这个。ruby-lang.org/en/news/2020/09/25/ruby-3-0-0-preview1-released

答:

255赞 dbryson 6/3/2011 #1

为什么不直接使用:

hash.delete(key)

hash现在是您正在寻找的“剩余哈希值”。

评论

4赞 Misha Moroshko 6/3/2011
@dbryson:我同意有时这不值得。我只是想知道为什么有,,,但没有......mergemerge!deletedetele!
1赞 Bert Goethals 6/4/2011
如果您真的需要它作为单班轮,请执行以下操作:foo(hash.delete(key) || hash)
31赞 David J. 7/27/2012
如果不修改其参数,并且存在并修改其参数,它将更符合 Ruby 约定。deletedelete!
96赞 MhdSyrwan 7/9/2015
这不会返回问题中提到的剩余哈希值,它将返回与已删除键关联的值。
4赞 Jackson Ray Hamilton 9/28/2017
@DavidJ。与普遍的看法相反,感叹号并不表示突变,而是表示“异常行为”。我想没有,因为变异的密钥删除方法似乎是唯一的“预期”行为。delete!
30赞 Max Williams 6/3/2011 #2
#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 返回删除了键的哈希副本,而 remove!修改哈希本身。这符合 ruby 约定。例如,从控制台

>> 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)
=> {}
27赞 rewritten 10/13/2011 #3

您可以从 gem 中使用:except!facets

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

原始哈希值不会更改。

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

评论

0赞 trans 1/6/2016
只是并且没有“问题”(除了不是 100% AS API 之外,不确定它们会是什么问题)。如果你正在做一个 Rails 项目,使用 AS 是有意义的,如果不是这样,Facets 的占用空间要小得多。require 'facets/hash/except'
0赞 rewritten 1/7/2016
@trans ActiveSupport 现在的占用空间也很小,您只需要其中的一部分。就像 facets 一样,但有更多的眼睛盯着它(所以我想它会得到更好的评价)。
244赞 Fabio 1/27/2012 #4

Oneliner 普通红宝石,它仅适用于 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 方法始终返回调用的对象...

否则,如果您有 required(在每个 Rails 应用程序中都是自动需要的),您可以根据需要使用以下方法之一:active_support/core_ext/hash

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

评论

19赞 Mulan 7/23/2013
+1 值得一提的是,这种方法对 . 不会修改原始哈希值。hHash#except
9赞 Jimbali 11/14/2018
用于避免修改原始哈希值。h.dup.tap { |hs| hs.delete(:a) }
885赞 Peter Brown 6/20/2012 #5

对于那些刚刚来到这里知道如何从哈希中删除键/值对的人来说,您可以使用:
hash.delete(key)

对于其他来这里阅读关于完全不同事物的文字墙的人,您可以阅读此答案的其余部分:

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

评论

59赞 Fryie 9/27/2013
您不必使用完整的 Rails 堆栈。您可以在任何 Ruby 应用程序中包含包含 ActiveSupport。
13赞 GMA 5/13/2016
为了补充 Fryie 的答案,您甚至不需要加载所有 ActiveSupport;然后你可以把它们包括在内require "active_support/core_ext/hash/except"
0赞 GMA 5/13/2016
编辑为时已晚:我的意思是“包括宝石”而不是“包括它们”
2赞 iconoclast 5/14/2020
@GMA:当您的五分钟编辑时间结束时,您可以随时复制、删除、修改和重新发布评论。
0赞 stevec 2/12/2023
一个很好的例子: User.first.as_json.except("id")
18赞 gamov 9/7/2012 #6

在纯 Ruby 中:

{:a => 1, :b => 2}.tap{|x| x.delete(:a)}   # => {:b=>2}
39赞 Yura Taras 12/19/2012 #7

如果你想使用纯 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}
1赞 frediy 1/12/2014 #8

这是一行方法,但可读性不强。建议改用两条线。

use_remaining_hash_for_something(Proc.new { hash.delete(:key); hash }.call)

评论

1赞 Michael Kohl 1/13/2014
Hash#except并且已经提到得够多了。正如您提到的,该版本的可读性不是很强,也比 .也许只是删除这个答案。Hash#except!Proc.newuse_remaining_hash_for_something(begin hash.delete(:key); hash end)
1赞 frediy 1/14/2014
缩短了我的回答并删除了已经说过的内容。将我的答案与您的评论放在一起,因为它们回答了问题并提出了很好的使用建议。
-13赞 fdghdfg 2/3/2014 #9

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

评论

3赞 obaqueiro 5/3/2014
h = {:a => 1, :b => 2, :c => 3};h[:a]=无;h.each{|k,v| put k} 不等同于: h = {:a => 1, :b => 2, :c => 3};h.删除(:a);h.each{|k,v| 把 k}
1赞 Sebastián Palma 11/9/2018
从哈希中删除键与从哈希中删除键的值不同。由于这可能会导致人们混淆,因此最好删除此答案。
13赞 Nakilon 4/7/2014 #10

请参阅 Ruby on Rails:删除多个哈希键

hash.delete_if{ |k,| keys_to_delete.include? k }

评论

0赞 Vignesh Jayavel 12/29/2015
keys_to_delete.each {|k| hash.delete(k)} 对于大型数据集要快得多。如果错了,请纠正我。
0赞 Nakilon 12/1/2019
@VigneshJayavel,你是对的,但 OP 希望返回哈希值。 将返回数组。each
21赞 Mohamad 12/13/2014 #11

如果您使用的是 Ruby 2,则可以使用优化来代替 monkey 修补或不必要地包含大型库:

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
137赞 techdreams 8/30/2016 #12

有很多方法可以从哈希中删除密钥并获取 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=> 它用于从哈希中删除所有值。如果要永久删除这些值,请使用 Use,否则使用 simple .nilcompact!nilcompact

    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。

评论

21赞 Madis Nõmme 10/10/2016
slice并使用 添加。它们不是 Ruby 核心的一部分。它们可以被用于exceptActiveSupport::CoreExtensions::Hashrequire 'active_support/core_ext/hash'
8赞 Madis Nõmme 11/12/2019
由于 Ruby 2.5 在标准库中。ruby-doc.org/core-2.5.0/Hash.html#method-i-slice耶!Hash#slice
5赞 frenesim 9/2/2018 #13

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

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

评论

0赞 Narfanator 7/21/2023
我找到了一个很好的方法,检查我的答案:)
1赞 Ketan Mangukiya 5/14/2019 #14

删除 Hash 中的 Key 的多种方法。 您可以使用下面的任何方法

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 文档。

谢谢

35赞 Marian13 10/4/2020 #15

哈希#except (Ruby 3.0+)

从 Ruby 3.0 开始,Hash#except 是一个内置方法。

因此,不再需要依赖 ActiveSupport 或编写 monkey-patches 来使用它。

h = { a: 1, b: 2, c: 3 }
p h.except(:a) #=> {:b=>2, :c=>3}

来源:

评论

1赞 MatzFan 10/4/2023
SO 需要一种方法来确保以后的答案可以突出显示,因为代码库已经改进,因此可以突出显示提供最佳答案。这个答案在 2023 年的 6 小时内萎靡不振,这真是太疯狂了。将把这个问题作为一个元问题提出来。
4赞 Sachin Singh 4/5/2021 #16

试试这个方法。except!

{:a => 1, :b => 2}.except!(:a)   #=> {:b => 2}
2赞 Jibran Usman 7/6/2023 #17

使用 、 或deleteexceptexcept!

sample_hash = {hey: 'hey', hello: 'hello'}

删除:

sample_hash.delete(:hey)
=> 'hey'

sample_hash
=> {hello: 'hello'}

返回键的值并删除原始对象中的键,如果没有这样的键,则返回 nil

除了:

sample_hash.except(:hey)
=> {hello: 'hello'}

sample_hash
=> {hey: 'hey', hello: 'hello'}

返回没有指定键的整个哈希值,但不更新原始哈希值

Except!: 与 except 相同,但它会像所有 bang 操作方法一样永久更改原始哈希的状态except!

sample_hash.except!(:hey)
=> {hello: 'hello'}

sample_hash
=> {hello: 'hello'}
0赞 Narfanator 7/21/2023 #18

我想删除一个键列表,并取回已删除的哈希“切片”:

导轨:

hash = {a: 1, b: 2, c: 3}

def delete_slice!(hash, *keys)
  hash.slice(*keys).tap { hash.except!(*keys) }
end

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

纯红宝石:

hash = {a: 1, b: 2, c: 3}

def delete_slice!(hash, *keys)
  hash.slice(*keys).tap { keys.each{ hash.delete _1 } }
end

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