Làm cách nào để xóa khóa khỏi Hash và nhận băm còn lại trong Ruby / Rails?


560

Để thêm một cặp mới vào Hash tôi làm:

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

Có cách nào tương tự để xóa khóa khỏi Hash không?

Những công việc này:

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

nhưng tôi hy vọng sẽ có một cái gì đó như:

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

Điều quan trọng là giá trị trả về sẽ là giá trị băm còn lại, vì vậy tôi có thể làm những việc như:

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

trong một dòng


1
Bạn luôn có thể mở rộng (mở khi chạy) Hash tích hợp để thêm phương thức tùy chỉnh này nếu bạn thực sự cần nó.
dbryson

Câu trả lời:


750

Rails có một ngoại lệ / ngoại trừ! phương thức trả về hàm băm với các khóa đó bị loại bỏ. Nếu bạn đã sử dụng Rails, sẽ không có ý nghĩa gì trong việc tạo phiên bản này của riêng bạn.

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
Bạn không phải sử dụng ngăn xếp Rails đầy đủ. Bạn có thể bao gồm ActiveSupport trong bất kỳ ứng dụng Ruby nào.
Fryie

10
Để thêm vào câu trả lời của Fry, bạn thậm chí không cần phải tải tất cả ActiveSupport; bạn chỉ có thể bao gồm chúng sau đórequire "active_support/core_ext/hash/except"
GMA

quá muộn để chỉnh sửa: Tôi có nghĩa là "bao gồm đá quý" chứ không phải "bao gồm chúng"
GMA

@GMA: khi bạn chỉnh sửa năm phút, bạn luôn có thể sao chép, xóa, sửa đổi và đăng lại nhận xét.
iconoclast

212

Oneliner đồng bằng ruby, nó chỉ hoạt động với 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 phương pháp luôn luôn trả lại đối tượng trên được gọi ...

Mặt khác, nếu bạn có yêu cầu active_support/core_ext/hash(được yêu cầu tự động trong mọi ứng dụng Rails), bạn có thể sử dụng một trong các phương pháp sau tùy thuộc vào nhu cầu của bạn:

  ~  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} 

ngoại trừ sử dụng cách tiếp cận danh sách đen, do đó, nó sẽ loại bỏ tất cả các khóa được liệt kê dưới dạng args, trong khi lát cắt sử dụng cách tiếp cận danh sách trắng, do đó, nó sẽ xóa tất cả các khóa không được liệt kê dưới dạng đối số. Ngoài ra còn tồn tại phiên bản bang của các phương thức đó ( except!slice!) sửa đổi hàm băm đã cho nhưng giá trị trả về của chúng khác nhau cả hai đều trả về một hàm băm. Nó đại diện cho các phím bị loại bỏ slice!và các phím được giữ cho 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 Điều đáng nói là phương pháp này mang tính hủy diệt h. Hash#exceptsẽ không sửa đổi hàm băm ban đầu.
Cảm ơn bạn

3
Sử dụng h.dup.tap { |hs| hs.delete(:a) }để tránh sửa đổi băm ban đầu.
Magicode

181

Tại sao không chỉ sử dụng:

hash.delete(key)

2
@dbryson: Tôi đồng ý rằng đôi khi nó không xứng đáng. Tôi chỉ tự hỏi tại sao có merge, merge!, delete, nhưng không có detele!...
Misha Moroshko

1
nếu bạn thực sự cần nó như một lớp lót, hãy làm:foo(hash.delete(key) || hash)
Bert Goethals

13
Nó sẽ là phù hợp hơn với công ước của Ruby nếu deleteđã không thay đổi thông số của nó và nếu delete!tồn tại và đã làm thay đổi thông số của nó.
David J.

60
Điều này không trả về hàm băm còn lại như được đề cập trong câu hỏi, nó sẽ trả về giá trị được liên kết với khóa đã xóa.
MhdSyrwan

1
xóa trả về khóa nhưng nó cũng làm thay đổi hàm băm. Về lý do tại sao không có xóa!, Tôi đoán là về mặt ngữ nghĩa không có nghĩa là gọi xóa trên một cái gì đó và không thực sự xóa nó. gọi hàm hash.delete () trái ngược với hash.delete! () sẽ là số không.
eggmatters 7/8/2015

85

Có nhiều cách để xóa khóa khỏi hàm băm và lấy số băm còn lại trong Ruby.

  1. .slice=> Nó sẽ trả về các khóa đã chọn và không xóa chúng khỏi hàm băm ban đầu. Sử dụng slice!nếu bạn muốn loại bỏ các khóa vĩnh viễn khác sử dụng đơn giản 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 => Nó sẽ xóa các khóa đã chọn khỏi hàm băm ban đầu (nó chỉ có thể chấp nhận một khóa và không nhiều hơn một khóa).

    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=> Nó sẽ trả về các khóa còn lại nhưng không xóa bất cứ thứ gì khỏi hàm băm ban đầu. Sử dụng except!nếu bạn muốn loại bỏ các khóa vĩnh viễn khác sử dụng đơn giản 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=> Trong trường hợp bạn cần xóa khóa dựa trên một giá trị. Nó rõ ràng sẽ loại bỏ các khóa phù hợp từ hàm băm ban đầu.

    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=> Nó được sử dụng để loại bỏ tất cả các nilgiá trị khỏi hàm băm. Sử dụng compact!nếu bạn muốn loại bỏ các nilgiá trị vĩnh viễn khác sử dụng đơn giản 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}

Kết quả dựa trên Ruby 2.2.2.


16
sliceexceptđược thêm vào bằng cách sử dụng ActiveSupport::CoreExtensions::Hash. Chúng không phải là một phần của lõi Ruby. Chúng có thể được sử dụng bởirequire 'active_support/core_ext/hash'
Madis Nõmme

3
Vì Ruby 2.5 Hash#slicenằm trong thư viện chuẩn. ruby-doc.org/core-2.5.0/Hash.html#method-i-slice Yay!
Madis Nõmme

38

Nếu bạn muốn sử dụng Ruby thuần túy (không có Rails), đừng muốn tạo các phương thức mở rộng (có thể bạn chỉ cần điều này ở một hoặc hai nơi và không muốn làm ô nhiễm không gian tên với hàng tấn phương thức) và không muốn chỉnh sửa hàm băm tại chỗ (nghĩa là bạn là người hâm mộ lập trình chức năng như tôi), bạn có thể 'chọn':

>> 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'

Tôi đã thiết lập điều này để .remove trả về một bản sao của hàm băm với các phím bị xóa trong khi xóa! tự sửa đổi hàm băm. Điều này phù hợp với các công ước ruby. ví dụ: từ bàn điều khiển

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

Bạn có thể sử dụng except!từ facetsđá quý:

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

Băm ban đầu không thay đổi.

EDIT: như Russel nói, các khía cạnh có một số vấn đề tiềm ẩn và không hoàn toàn tương thích API với ActiveSupport. Mặt khác, ActiveSupport không hoàn chỉnh như các khía cạnh. Cuối cùng, tôi sẽ sử dụng AS và để các trường hợp cạnh trong mã của bạn.


Chỉ require 'facets/hash/except'và họ không có "vấn đề" (không chắc họ sẽ gặp vấn đề gì ngoài việc không phải API 100% AS). Nếu bạn đang thực hiện một dự án Rails bằng AS có ý nghĩa, nếu không Facets có dấu chân nhỏ hơn nhiều.
xuyên

@trans ActiveSupport hiện nay cũng có một dấu chân khá nhỏ và bạn chỉ có thể yêu cầu một phần của nó. Cũng giống như các khía cạnh, nhưng với nhiều con mắt hơn về nó (vì vậy tôi cho rằng nó được đánh giá tốt hơn).
viết lại

19

Thay vì vá khỉ hoặc không cần thiết bao gồm các thư viện lớn, bạn có thể sử dụng các sàng lọc nếu bạn đang sử dụng 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

Bạn có thể sử dụng tính năng này mà không ảnh hưởng đến các phần khác trong chương trình của bạn hoặc phải bao gồm các thư viện lớn bên ngoài.

class FabulousCode
  using HashExtensions

  def incredible_stuff
    delightful_hash.except(:not_fabulous_key)
  end
end

17

trong Ruby nguyên chất:

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


3

Thật tuyệt nếu xóa trả lại cặp xóa băm. Tôi đang làm điều này:

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

1

Đây là một cách để thực hiện nó, nhưng nó không dễ đọc lắm. Đề nghị sử dụng hai dòng thay thế.

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

1
Hash#exceptHash#except!đã được đề cập đủ rồi. Các Proc.newphiên bản không phải là rất có thể đọc được như bạn đề cập và cũng phức tạp hơn use_remaining_hash_for_something(begin hash.delete(:key); hash end). Có lẽ chỉ cần xóa câu trả lời này.
Michael Kohl

1
Rút ngắn câu trả lời của tôi và loại bỏ những gì đã được nói. Giữ câu trả lời của tôi cùng với nhận xét của bạn vì họ trả lời câu hỏi và đưa ra khuyến nghị tốt để sử dụng.
the_minted

0

Nhiều cách để xóa Key trong Hash. bạn có thể sử dụng bất kỳ Phương pháp nào từ bên dưới

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

Có rất nhiều cách, bạn có thể xem Ruby doc of Hash tại đây .

Cảm ơn bạn


-12

Điều này cũng sẽ làm việc: hash[hey] = nil


3
h = {: a => 1 ,: b => 2 ,: c => 3}; h [: a] = nil; h.each {| k, v | đặt k} Không giống như: h = {: a => 1 ,: b => 2 ,: c => 3}; h.delete (: a); h.each {| k, v | đặt k}
obaqueiro

1
Để xóa khóa khỏi hàm băm không giống như xóa giá trị của khóa khỏi hàm băm. Vì điều này có thể khiến mọi người nhầm lẫn, tốt hơn hết là xóa câu trả lời này.
Sebastian Palma
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.