Jak usunąć klucz z Hash i uzyskać pozostały skrót w Ruby / Rails?


560

Aby dodać nową parę do Hash, robię:

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

Czy istnieje podobny sposób na usunięcie klucza z Hash?

To działa:

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

ale oczekiwałbym czegoś takiego:

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

Ważne jest, aby zwracaną wartością był pozostały skrót, więc mogłem wykonywać następujące czynności:

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

w jednej linii.


1
Zawsze możesz rozszerzyć (otwierać w czasie wykonywania) wbudowaną funkcję mieszania, aby dodać tę niestandardową metodę, jeśli naprawdę jej potrzebujesz.
dbryson

Odpowiedzi:


750

Railsy mają wyjątek / wyjątek! Metoda, która zwraca skrót z usuniętymi kluczami. Jeśli już używasz Railsów, nie ma sensu tworzyć własnej wersji tego.

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
Nie musisz używać pełnego stosu Railsów. Możesz dołączyć ActiveSupport do dowolnej aplikacji Ruby.
Fryie

10
Aby dodać odpowiedź Fryie, nie musisz nawet ładować całego ActiveSupport; możesz je wtedy dołączyćrequire "active_support/core_ext/hash/except"
GMA

za późno na edycję: miałem na myśli „dołącz klejnot”, a nie „dołącz je”
GMA

@GMA: po upływie pięciu minut edycji zawsze możesz skopiować, usunąć, zmodyfikować i ponownie opublikować komentarz.
iconoclast

211

Oneliner zwykły rubin, działa tylko z 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} 

Metoda dotknij zawsze zwraca obiekt, na który jest wywoływany ...

W przeciwnym razie, jeśli jest to wymagane active_support/core_ext/hash(co jest automatycznie wymagane w każdej aplikacji Railsowej), możesz użyć jednej z następujących metod w zależności od potrzeb:

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

oprócz wykorzystuje podejście z czarnej listy, więc usuwa wszystkie klucze wymienione jako argumenty, a slice używa podejścia z białej listy, więc usuwa wszystkie klucze, które nie są wymienione jako argumenty. Istnieje również wersja Bang tej metody ( except!i slice!), która modyfikuje dany skrót, ale ich wartość zwracana jest inna, obie zwracają skrót. Reprezentuje usunięte klucze slice!i klucze przechowywane dla 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 Warto wspomnieć, że ta metoda działa destrukcyjnie h. Hash#exceptnie zmodyfikuje oryginalnego skrótu.
Dziękuję

3
Użyj, h.dup.tap { |hs| hs.delete(:a) }aby uniknąć modyfikacji oryginalnego skrótu.
Magicode,

181

Dlaczego nie po prostu użyć:

hash.delete(key)

2
@dbryson: Zgadzam się, że czasami nie warto. Zastanawiam się tylko, dlaczego tam są merge, merge!, delete, ale nie detele!...
Misha Moroshko

1
jeśli naprawdę potrzebujesz tego jako jednego liniowca, wykonaj:foo(hash.delete(key) || hash)
Bert Goethals

13
To byłoby bardziej zgodne z konwencjami Ruby jeśli deletenie nie zmodyfikować jego parametry, a jeśli delete!istniał i tak zmodyfikować jego parametry.
David J.

60
Nie zwróci to pozostałego skrótu, jak wspomniano w pytaniu, zwróci wartość powiązaną z usuniętym kluczem.
MhdSyrwan

1
delete zwraca klucz, ale zmienia również skrót. Jeśli chodzi o to, dlaczego nie ma usuwania !, przypuszczam, że semantycznie nie ma sensu wywoływać usuwania na czymś, a nie usuwanie go. wywołanie hash.delete () w przeciwieństwie do hash.delete! () nie byłoby możliwe.
eggmatters,

85

Istnieje wiele sposobów na usunięcie klucza z skrótu i ​​uzyskanie pozostałego skrótu w Ruby.

  1. .slice=> Zwróci wybrane klucze i nie usunie ich z oryginalnego skrótu. Użyj, slice!jeśli chcesz usunąć klucze na stałe, użyj prostego 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 => Spowoduje to usunięcie wybranych kluczy z oryginalnego skrótu (może zaakceptować tylko jeden klucz i nie więcej niż jeden).

    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=> Zwróci pozostałe klucze, ale nie usunie niczego z oryginalnego skrótu. Użyj, except!jeśli chcesz usunąć klucze na stałe, użyj prostego 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=> W przypadku, gdy musisz usunąć klucz na podstawie wartości. Oczywiście usunie pasujące klucze z oryginalnego skrótu.

    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=> Służy do usuwania wszystkich nilwartości z mieszania. Użyj, compact!jeśli chcesz niltrwale usunąć wartości, w innym przypadku użyj prostego 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}

Wyniki oparte na Ruby 2.2.2.


16
slicei exceptsą dodawane przy użyciu ActiveSupport::CoreExtensions::Hash. Nie są częścią rdzenia Ruby. Mogą z nich korzystaćrequire 'active_support/core_ext/hash'
Madis Nõmme,

3
Ponieważ Ruby 2.5 Hash#sliceznajduje się w standardowej bibliotece. ruby-doc.org/core-2.5.0/Hash.html#method-i-slice Yay!
Madis Nõmme

38

Jeśli chcesz używać czystego Ruby (bez Railsów), nie chcesz tworzyć metod rozszerzeń (być może potrzebujesz tego tylko w jednym lub dwóch miejscach i nie chcesz zanieczyszczać przestrzeni nazw tonami metod) i nie chcesz edytować skrót w miejscu (tzn. jesteś fanem programowania funkcjonalnego takiego jak ja), możesz „wybrać”:

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

Skonfigurowałem to tak, aby .remove zwrócił kopię skrótu z usuniętymi kluczami podczas usuwania! modyfikuje sam skrót. Jest to zgodne z konwencjami rubinowymi. np. z konsoli

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

Możesz użyć except!z facetsklejnotu:

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

Oryginalny skrót nie zmienia się.

EDYCJA: jak mówi Russel, aspekty mają pewne ukryte problemy i nie są całkowicie kompatybilne z API z ActiveSupport. Z drugiej strony ActiveSupport nie jest tak kompletny jak aspekty. Na koniec użyłbym AS i pozwoliłbym, by w twoim kodzie były małe litery.


Po prostu require 'facets/hash/except'i nie są to „problemy” (nie jestem pewien, jakie problemy i tak by były, poza tym, że nie 100% AS API). Jeśli wykonujesz projekt Railsowy przy użyciu AS ma sens, jeśli nie, Facets ma znacznie mniejszą powierzchnię.
trans

@trans ActiveSupport ma obecnie również dość mały ślad i możesz wymagać tylko jego części. Podobnie jak aspekty, ale z wieloma innymi oczami (więc przypuszczam, że dostaje lepsze recenzje).
przepisany

19

Zamiast łatania małp lub niepotrzebnego dołączania dużych bibliotek, możesz użyć udoskonaleń, jeśli używasz 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

Możesz korzystać z tej funkcji bez wpływu na inne części programu lub konieczności dołączania dużych bibliotek zewnętrznych.

class FabulousCode
  using HashExtensions

  def incredible_stuff
    delightful_hash.except(:not_fabulous_key)
  end
end

17

w czystym Ruby:

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


3

Świetnie było, jeśli funkcja delete zwraca parę usuwania z mieszania. Robię to:

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

1

Jest to jeden wiersz, aby to zrobić, ale nie jest bardzo czytelny. Polecam zamiast tego użyć dwóch linii.

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

1
Hash#excepti Hash#except!zostały już wystarczająco wspomniane. Proc.newWersja nie jest bardzo czytelna, jak wspomniałeś, a także bardziej skomplikowane niż use_remaining_hash_for_something(begin hash.delete(:key); hash end). Może po prostu usuń tę odpowiedź.
Michael Kohl

1
Skróciłem moją odpowiedź i usunąłem to, co już powiedziano. Zachowuję moją odpowiedź wraz z twoim komentarzem, ponieważ odpowiadają na pytanie i dają dobre rekomendacje dotyczące użycia.
the_minted

0

Wiele sposobów usuwania klucza w skrócie. możesz użyć dowolnej z poniższych metod

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

Jest tak wiele sposobów, możesz spojrzeć na Ruby doc Hash tutaj .

Dziękuję Ci


-12

Działa to również: hash[hey] = nil


3
h = {: a => 1,: b => 2,: c => 3}; h [: a] = zero; h.each {| k, v | wstawia k} Nie jest tym samym, co: h = {: a => 1,: b => 2,: c => 3}; h.delete (: a); h.each {| k, v | puts k}
obaqueiro

1
Usunięcie klucza z skrótu nie jest tym samym, co usunięcie wartości klucza z skrótu. Ponieważ może to prowadzić do zamieszania, lepiej byłoby usunąć tę odpowiedź.
Sebastian Palma
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.