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.


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

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} 

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)

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.


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}

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)
=> {}

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.


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

17

in reinem Rubin:

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


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} 

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)

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


-12

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


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
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.