Wie entferne ich einen Schlüssel aus dem Hash und erhalte den verbleibenden Hash in Ruby / Rails?

560

Um Hash ein neues Paar hinzuzufügen, gehe ich wie folgt vor:

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

Gibt es eine ähnliche Möglichkeit, einen Schlüssel aus Hash zu löschen?

Das funktioniert:

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

aber ich würde erwarten, etwas zu haben wie:

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

Es ist wichtig, dass der Rückgabewert der verbleibende Hash ist, damit ich Dinge tun kann wie:

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

in einer Zeile.

Mischa Moroshko
quelle
1
Sie können den integrierten Hash jederzeit erweitern (zur Laufzeit öffnen), um diese benutzerdefinierte Methode hinzuzufügen, wenn Sie sie wirklich benötigen.
Dbryson

Antworten:

750

Schienen hat eine Ausnahme / Ausnahme! Methode , die den Hash mit diesen entfernten Schlüsseln zurückgibt. Wenn Sie bereits Rails verwenden, macht es keinen Sinn, eine eigene Version davon zu erstellen.

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
quelle
51
Sie müssen nicht den vollständigen Rails-Stack verwenden. Sie können ActiveSupport in jede Ruby-Anwendung aufnehmen.
Fryie
10
Um Fryies Antwort zu ergänzen, müssen Sie nicht einmal ActiveSupport vollständig laden. Sie können sie dann einfach einschließenrequire "active_support/core_ext/hash/except"
GMA
zu spät zum Bearbeiten: Ich meinte "den Edelstein einschließen" nicht "sie einschließen"
GMA
@GMA: Wenn Ihre fünf Minuten Bearbeitungszeit abgelaufen sind, können Sie einen Kommentar jederzeit kopieren, löschen, ändern und erneut veröffentlichen.
Bilderstürmer
212

Oneliner normaler Rubin, funktioniert nur mit Rubin> 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- Methode gibt immer das Objekt zurück, für das aufgerufen wird ...

Andernfalls können Sie bei Bedarf active_support/core_ext/hash(was in jeder Rails-Anwendung automatisch erforderlich ist) je nach Ihren Anforderungen eine der folgenden Methoden verwenden:

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

Ausgenommen wird ein Blacklist-Ansatz verwendet, sodass alle als Argumente aufgelisteten Schlüssel entfernt werden , während Slice einen Whitelist-Ansatz verwendet, sodass alle Schlüssel entfernt werden, die nicht als Argumente aufgeführt sind. Es gibt auch die Bang-Version dieser Methoden ( except!und slice!), die den angegebenen Hash ändern, aber ihr Rückgabewert ist unterschiedlich. Beide geben einen Hash zurück. Es stellt die entfernten Schlüssel für slice!und die Schlüssel dar, die für Folgendes aufbewahrt werden 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
quelle
18
+1 Es ist erwähnenswert, dass diese Methode destruktiv ist h. Hash#exceptändert den ursprünglichen Hash nicht.
Danke
3
Verwenden Sie h.dup.tap { |hs| hs.delete(:a) }diese Option , um zu vermeiden, dass der ursprüngliche Hash geändert wird.
Magicode
181

Warum nicht einfach verwenden:

hash.delete(key)
dbryson
quelle
2
@dbryson: Ich stimme zu, dass es sich manchmal nicht lohnt. Ich frage mich nur , warum es merge, merge!, delete, aber nicht detele!...
Misha Moroshko
1
Wenn Sie es wirklich als Einzeiler brauchen, tun Sie Folgendes:foo(hash.delete(key) || hash)
Bert Goethals
13
Es wäre konsistenter mit Ruby-Konventionen, wenn deletees seinen Parameter nicht ändern würde und wenn es delete!existiert und seinen Parameter ändern würde.
David J.
60
Dies gibt nicht den verbleibenden Hash zurück, wie in der Frage erwähnt, sondern den Wert, der dem gelöschten Schlüssel zugeordnet ist.
MhdSyrwan
1
delete gibt den Schlüssel zurück, ändert aber auch den Hash. Ich vermute, dass es semantisch nicht sinnvoll ist, delete für etwas aufzurufen und es nicht tatsächlich zu löschen. Das Aufrufen von hash.delete () im Gegensatz zu hash.delete! () wäre ein No-Op.
Eggmatters
85

Es gibt viele Möglichkeiten, einen Schlüssel aus einem Hash zu entfernen und den verbleibenden Hash in Ruby abzurufen.

  1. .slice=> Es werden ausgewählte Schlüssel zurückgegeben und nicht aus dem ursprünglichen Hash gelöscht. Verwenden slice!Sie diese Option, wenn Sie die Schlüssel dauerhaft entfernen möchten. Andernfalls verwenden Sie einfach 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 => Es löscht die ausgewählten Schlüssel aus dem ursprünglichen Hash (es kann nur einen Schlüssel und nicht mehr als einen akzeptieren).

    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=> Es werden die verbleibenden Schlüssel zurückgegeben, aber nichts aus dem ursprünglichen Hash gelöscht. Verwenden except!Sie diese Option, wenn Sie die Schlüssel dauerhaft entfernen möchten. Andernfalls verwenden Sie einfach 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=> Falls Sie einen Schlüssel basierend auf einem Wert entfernen müssen. Es werden offensichtlich die passenden Schlüssel aus dem ursprünglichen Hash entfernt.

    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=> Es wird verwendet, um alle nilWerte aus dem Hash zu entfernen . Verwenden compact!Sie diese Option, wenn Sie die nilWerte dauerhaft entfernen möchten. Andernfalls verwenden Sie einfach 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}

Ergebnisse basierend auf Ruby 2.2.2.

Techdreams
quelle
16
sliceund exceptwerden mit hinzugefügt ActiveSupport::CoreExtensions::Hash. Sie sind nicht Teil des Ruby-Kerns. Sie können vonrequire 'active_support/core_ext/hash'
Madis Nõmme
3
Da Ruby 2.5 Hash#slicein der Standardbibliothek ist. ruby-doc.org/core-2.5.0/Hash.html#method-i-slice Yay!
Madis Nõmme
38

Wenn Sie reines Ruby (keine Rails) verwenden möchten, möchten Sie keine Erweiterungsmethoden erstellen (möglicherweise benötigen Sie diese nur an ein oder zwei Stellen und möchten den Namespace nicht mit Tonnen von Methoden verschmutzen) und möchten dies nicht Wenn Sie den Hash an Ort und Stelle bearbeiten (dh Sie sind ein Fan von funktionaler Programmierung wie ich), können Sie Folgendes auswählen:

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

Ich habe dies so eingerichtet, dass .remove eine Kopie des Hashs mit entfernten Schlüsseln zurückgibt, während remove! Ändert den Hash selbst. Dies steht im Einklang mit Rubinkonventionen. zB von der Konsole

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

Sie können except!aus dem facetsEdelstein verwenden:

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

Der ursprüngliche Hash ändert sich nicht.

BEARBEITEN: Wie Russel sagt, haben Facetten einige versteckte Probleme und sind nicht vollständig API-kompatibel mit ActiveSupport. Auf der anderen Seite ist ActiveSupport nicht so vollständig wie Facetten. Am Ende würde ich AS verwenden und die Randfälle in Ihrem Code lassen.

umgeschrieben
quelle
Nur require 'facets/hash/except'und es gibt keine "Probleme" (nicht sicher, welche Probleme sie sonst wären, außer nicht 100% AS API). Wenn Sie ein Rails-Projekt mit AS durchführen, ist dies sinnvoll, wenn nicht, hat Facets einen viel geringeren Platzbedarf.
Trans
@trans ActiveSupport hat heutzutage auch einen relativ geringen Platzbedarf, und Sie können nur Teile davon benötigen. Genau wie Facetten, aber mit viel mehr Augen (also nehme ich an, dass es bessere Kritiken gibt).
umgeschrieben am
19

Anstatt Ruby zu patchen oder unnötigerweise große Bibliotheken einzuschließen, können Sie Verfeinerungen verwenden, wenn Sie Ruby 2 verwenden :

module HashExtensions
  refine Hash do
    def except!(*candidates)
      candidates.each { |candidate| delete(candidate) }
      self
    end

    def except(*candidates)
      dup.remove!(candidates)
    end
  end
end

Sie können diese Funktion verwenden, ohne andere Teile Ihres Programms zu beeinträchtigen oder große externe Bibliotheken einzuschließen.

class FabulousCode
  using HashExtensions

  def incredible_stuff
    delightful_hash.except(:not_fabulous_key)
  end
end
Mohamad
quelle
17

in reinem Rubin:

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

Siehe Ruby on Rails: Löschen Sie mehrere Hash-Schlüssel

hash.delete_if{ |k,| keys_to_delete.include? k }
Nakilon
quelle
keys_to_delete.each {| k | hash.delete (k)} ist für große Datenmengen viel schneller. korrigiere mich wenn falsch.
Vignesh Jayavel
@VigneshJayavel, Sie haben Recht, aber OP wollte, dass der Hash zurückgegeben wird. eachwürde das Array zurückgeben.
Nakilon
3

Es war großartig, wenn delete das Löschpaar des Hashs zurückgibt. Ich mache das:

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

Dies ist eine einzeilige Methode, die jedoch nicht sehr gut lesbar ist. Empfehlen Sie stattdessen zwei Zeilen.

use_remaining_hash_for_something(Proc.new { hash.delete(:key); hash }.call)
the_minted
quelle
1
Hash#exceptund Hash#except!wurden schon genug erwähnt. Die Proc.newVersion ist, wie Sie erwähnen, nicht sehr lesbar und auch komplizierter als use_remaining_hash_for_something(begin hash.delete(:key); hash end). Vielleicht löschen Sie einfach diese Antwort.
Michael Kohl
1
Verkürzte meine Antwort und entfernte das, was bereits gesagt worden war. Behalten Sie meine Antwort zusammen mit Ihrem Kommentar bei, da sie die Frage beantworten und gute Empfehlungen für die Verwendung abgeben.
the_minted
0

Mehrere Möglichkeiten, Key in Hash zu löschen. Sie können eine beliebige Methode von unten verwenden

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

Es gibt so viele Möglichkeiten, dass Sie sich das Ruby-Dokument von Hash hier ansehen können .

Vielen Dank

Ketan Mangukiya
quelle
-12

Dies würde auch funktionieren: hash[hey] = nil

fdghdfg
quelle
3
h = {: a => 1 ,: b => 2 ,: c => 3}; h [: a] = null; h.each {| k, v | setzt k} Ist nicht dasselbe wie: h = {: a => 1 ,: b => 2 ,: c => 3}; h.delete (: a); h.each {| k, v | setzt k}
obaqueiro
1
Das Entfernen eines Schlüssels aus einem Hash ist nicht dasselbe wie das Entfernen des Werts eines Schlüssels aus einem Hash. Da dies zu Verwirrung führen kann, ist es besser, diese Antwort zu entfernen.
Sebastian Palma