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.

Misza Moroszko
źródło
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
Peter Brown
źródło
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} 
Fabio
źródło
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)
dbryson
źródło
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.

techdreams
źródło
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}
Yura Taras
źródło
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)
=> {}
Max Williams
źródło
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.

przepisane
źródło
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
Mohamad
źródło
17

w czystym Ruby:

{:a => 1, :b => 2}.tap{|x| x.delete(:a)}   # => {:b=>2}
gamov
źródło
13

Zobacz Ruby on Rails: Usuń wiele kluczy skrótu

hash.delete_if{ |k,| keys_to_delete.include? k }
Nakilon
źródło
keys_to_delete.each {| k | hash.delete (k)} jest znacznie szybszy dla dużych zestawów danych. popraw mnie jeśli źle.
Vignesh Jayavel
@VigneshJayavel, masz rację, ale OP chciał, aby skrót został zwrócony. eachzwróci tablicę.
Nakilon
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} 
frenesim
źródło
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)
the_minted
źródło
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

Ketan Mangukiya
źródło
-12

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

fdghdfg
źródło
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