Ruby: Einfachste Möglichkeit, Hash-Schlüssel zu filtern?

225

Ich habe einen Hash, der ungefähr so ​​aussieht:

params = { :irrelevant => "A String",
           :choice1 => "Oh look, another one",
           :choice2 => "Even more strings",
           :choice3 => "But wait",
           :irrelevant2 => "The last string" }

Und ich möchte auf einfache Weise alle Schlüssel ablehnen, die nicht Auswahl + int sind. Es könnte Wahl1 oder Wahl1 bis Wahl10 sein. Es variiert.

Wie kann ich die Tasten nur mit der Wortwahl und einer Ziffer oder Ziffern nach ihnen auswählen?

Bonus:

Verwandeln Sie den Hash in eine Zeichenfolge mit Tabulator (\ t) als Trennzeichen. Ich habe das gemacht, aber es dauerte mehrere Codezeilen. Normalerweise können Meister-Rubiker dies in einer oder mehreren Zeilen tun.

Derek
quelle
Können Sie in Bezug auf die Bonusfrage klarstellen, wie die Zeichenfolge aussehen soll?
Mikej
Sicher, der obige Beispiel-Hash würde ergeben: "Oh, sieh mal, noch eine \ tEven weitere Zeichenfolgen \ tAber warten" (mit nicht \ t am Ende der Zeichenfolge, nur zwischen ihnen)
Derek
Ihre Beispielzeichenfolge wurde im Kommentar abgeschnitten. Sie können den Bearbeitungslink verwenden, um Ihre Frage zu bearbeiten und das Beispiel dort hinzuzufügen. Sie können auch den Rubin, den Sie sich ausgedacht haben, als Beispiel dafür veröffentlichen, was Sie erreichen möchten.
Mikej
2
mögliches Duplikat von Ruby Hash Filter
jbochi

Antworten:

281

Auf ursprüngliche Antwort bearbeiten : Obwohl dies die Antwort (zum Zeitpunkt dieses Kommentars) die ausgewählte Antwort ist, ist die ursprüngliche Version dieser Antwort veraltet.

Ich füge hier ein Update hinzu, damit andere nicht von dieser Antwort abgelenkt werden, wie ich es getan habe.

Wie in der anderen Antwort erwähnt, hat Ruby> = 2.5 die Hash#sliceMethode hinzugefügt , die zuvor nur in Rails verfügbar war.

Beispiel:

> { one: 1, two: 2, three: 3 }.slice(:one, :two)
=> {:one=>1, :two=>2}

Ende der Bearbeitung. Was folgt, ist die ursprüngliche Antwort, die ich für nützlich halte, wenn Sie Ruby <2.5 ohne Rails verwenden, obwohl ich mir vorstelle, dass dieser Fall derzeit ziemlich ungewöhnlich ist.


Wenn Sie Ruby verwenden, können Sie die selectMethode verwenden. Sie müssen den Schlüssel von einem Symbol in einen String konvertieren, um die Regexp-Übereinstimmung durchzuführen. Dadurch erhalten Sie einen neuen Hash mit nur den darin enthaltenen Optionen.

choices = params.select { |key, value| key.to_s.match(/^choice\d+/) }

oder Sie können delete_ifden vorhandenen Hash verwenden und ändern, z

params.delete_if { |key, value| !key.to_s.match(/choice\d+/) }

oder wenn es nur die Schlüssel und nicht die gewünschten Werte sind, können Sie Folgendes tun:

params.keys.select { |key| key.to_s.match(/^choice\d+/) }

und dies gibt dem nur ein Array der Schlüssel, z [:choice1, :choice2, :choice3]

mikej
quelle
1
Ausgezeichnet! Sobald ich es sehe, ist das so einfach! Vielen Dank und vielen Dank an die anderen, die sich die Zeit genommen haben, auch zu antworten.
Derek
2
Symbole können heutzutage mit regulären Ausdrücken IIRC analysiert werden.
Andrew Grimm
@ Andrew, ich denke du hast recht. Ich war am 1.8.7, als ich die Antwort auf diese Frage testete.
Mikej
1
Das Gegenstück dazu .selectist .reject, wenn dies Ihren Code idiomatischer macht.
Joe Atzberger
Der #sliceWeg funktioniert bei mir auf 2.5.3 auch ohne aktive Unterstützung.
Graywolf
305

In Ruby ist die Auswahl von Hash # eine richtige Option. Wenn Sie mit Rails arbeiten, können Sie Hash # Slice und Hash # Slice! Verwenden. zB (Schienen 3.2.13)

h1 = {:a => 1, :b => 2, :c => 3, :d => 4}

h1.slice(:a, :b)         # return {:a=>1, :b=>2}, but h1 is not changed

h2 = h1.slice!(:a, :b)   # h1 = {:a=>1, :b=>2}, h2 = {:c => 3, :d => 4}
lfx_cool
quelle
12
Achten Sie auf Zeichenfolgen. h1 = {'a' => 1 ,: b => 2} gibt nur {: b => 2} mit h1.slice (: a ,: b) zurück
davidcollom
Tolle Lösung. Ich habe dies verwendet, um das Ergebnis in rspec zurückzugeben, wo ich einen Wert eines Hashs innerhalb eines Hashs analysieren wollte. `` `test = {: a => 1 ,: b => 2, c => {: A => 1 ,: B => 2}} solution ==> test.c.slice (: B)` ` `
PriyankaK
Ersetzen Sie Rails durch ActiveSupport und das ist perfekt;)
Geoffroy
5
Dies beantwortet die Frage jedoch nicht, da er eine Möglichkeit haben möchte, die Werte für Schlüssel zu extrahieren, die "choice + int" sind. Ihre Lösung kann nur im Voraus bekannte Schlüssel extrahieren.
Metakung Fu
46

Am einfachsten ist es, das gem 'activesupport'(oder gem 'active_support') einzuschließen .

Dann müssen Sie in Ihrer Klasse nur noch

require 'active_support/core_ext/hash/slice'

und anzurufen

params.slice(:choice1, :choice2, :choice3) # => {:choice1=>"Oh look, another one", :choice2=>"Even more strings", :choice3=>"But wait"}

Ich glaube, es lohnt sich nicht, andere Funktionen zu deklarieren, die möglicherweise Fehler aufweisen, und es ist besser, eine Methode zu verwenden, die in den letzten Jahren optimiert wurde.

Nuno Costa
quelle
Activerecord wird dafür mindestens ab 2.5 nicht benötigt.
Graywolf
13

Am einfachsten ist es, den Edelstein 'activesupport' (oder den Edelstein 'active_support') einzuschließen.

params.slice(:choice1, :choice2, :choice3)

翟英昌
quelle
4
Bitte fügen Sie Ihrer Antwort weitere Details hinzu.
Mohit Jain
13

Wenn Sie mit Schienen arbeiten und die Schlüssel in einer separaten Liste haben, können Sie die *Notation verwenden:

keys = [:foo, :bar]
hash1 = {foo: 1, bar:2, baz: 3}
hash2 = hash1.slice(*keys)
=> {foo: 1, bar:2}

Wie in anderen Antworten angegeben, können Sie auch slice!den vorhandenen Hash ändern (und den gelöschten Schlüssel / die gelöschten Werte zurückgeben).

Robert
quelle
9

Dies ist eine Zeile, um die vollständige ursprüngliche Frage zu lösen:

params.select { |k,_| k[/choice/]}.values.join('\t')

Die meisten der oben genannten Lösungen lösen jedoch einen Fall, in dem Sie die Schlüssel im sliceVoraus kennen müssen, indem Sie einen einfachen regulären Ausdruck verwenden.

Hier ist ein weiterer Ansatz , der für einfache und komplexere Anwendungsfälle funktioniert und zur Laufzeit ausgetauscht werden kann

data = {}
matcher = ->(key,value) { COMPLEX LOGIC HERE }
data.select(&matcher)

Dies ermöglicht nicht nur eine komplexere Logik beim Abgleichen der Schlüssel oder Werte, sondern ist auch einfacher zu testen, und Sie können die Übereinstimmungslogik zur Laufzeit austauschen.

Ex, um das ursprüngliche Problem zu lösen:

def some_method(hash, matcher) 
  hash.select(&matcher).values.join('\t')
end

params = { :irrelevant => "A String",
           :choice1 => "Oh look, another one",
           :choice2 => "Even more strings",
           :choice3 => "But wait",
           :irrelevant2 => "The last string" }

some_method(params, ->(k,_) { k[/choice/]}) # => "Oh look, another one\\tEven more strings\\tBut wait"
some_method(params, ->(_,v) { v[/string/]}) # => "Even more strings\\tThe last string"
Metakung Fu
quelle
1
Ich mag den Matcher wirklich
Calin
7

Mit Hash::select:

params = params.select { |key, value| /^choice\d+$/.match(key.to_s) }
Arnaud Le Blanc
quelle
6

Wenn Sie den verbleibenden Hash möchten:

params.delete_if {|k, v| ! k.match(/choice[0-9]+/)}

oder wenn Sie nur die Schlüssel wollen:

params.keys.delete_if {|k| ! k.match(/choice[0-9]+/)}
Ayckoster
quelle
Abhängig davon, wie viele choiceX und irrelevantX Sie ausgewählt haben, ist OR delete_if möglicherweise besser. Wenn Sie mehr Auswahl haben, ist delete_if besser.
Ayckoster
6

Setzen Sie dies in einen Initialisierer

class Hash
  def filter(*args)
    return nil if args.try(:empty?)
    if args.size == 1
      args[0] = args[0].to_s if args[0].is_a?(Symbol)
      self.select {|key| key.to_s.match(args.first) }
    else
      self.select {|key| args.include?(key)}
    end
  end
end

Dann können Sie tun

{a: "1", b: "b", c: "c", d: "d"}.filter(:a, :b) # =>  {a: "1", b: "b"}

oder

{a: "1", b: "b", c: "c", d: "d"}.filter(/^a/)  # =>  {a: "1"}
montrealmike
quelle
5
params.select{ |k,v| k =~ /choice\d/ }.map{ |k,v| v}.join("\t")
Puhlze
quelle
4

Was die Bonusfrage betrifft:

  1. Wenn Sie eine solche #selectMethode ausgegeben haben (Liste der 2-Element-Arrays):

    [[:choice1, "Oh look, another one"], [:choice2, "Even more strings"], [:choice3, "But wait"]]

    Nehmen Sie dann einfach dieses Ergebnis und führen Sie Folgendes aus:

    filtered_params.join("\t")
    # or if you want only values instead of pairs key-value
    filtered_params.map(&:last).join("\t")
  2. Wenn Sie eine Ausgabe von einer #delete_ifsolchen Methode (Hash) haben:

    {:choice1=>"Oh look, another one", :choice2=>"Even more strings", :choice3=>"But wait"}

    dann:

    filtered_params.to_a.join("\t")
    # or
    filtered_params.values.join("\t")
MBO
quelle
Tolle Lösungen. Eine Frage: Ist filtered_params.map (&: last) .join ("\ t") die Abkürzung für filtered_params.map (| i | i.last) .join ("\ t")? Wenn ja, was ist "zuletzt"? könnte ich &: value verwenden, um den Wert des Hash zu erhalten?
Derek
mapnimmt Block als Argument. Sie können einen Block im laufenden Betrieb mit einer &:methodKonstruktion erstellen, die einen Block erstellen würde {|v| v.method }. In meinem Fall &:lastwurde ein Array-Argument (2-Element-Array) aufgerufen. Wenn Sie damit spielen möchten, überprüfen Sie zuerst, welches Argument Sie in Block erhalten (erhalten Sie 1 Argument, dekonstruieren Sie es nicht in mehrere Argumente!) Und versuchen Sie, 1 Methode zu finden (mit der Sie keine Methoden verketten können &:method), die Ihnen das zurückgibt, was Sie sind wollen. Wenn es möglich ist, können Sie die Verknüpfung verwenden. Wenn nicht, benötigen Sie einen vollständigen Block
MBO
2
params = { :irrelevant => "A String",
           :choice1 => "Oh look, another one",
           :choice2 => "Even more strings",
           :choice3 => "But wait",
           :irrelevant2 => "The last string" }

choices = params.select { |key, value| key.to_s[/^choice\d+/] }
#=> {:choice1=>"Oh look, another one", :choice2=>"Even more strings", :choice3=>"But wait"}
Arup Rakshit
quelle
0

Ich hatte ein ähnliches Problem. In meinem Fall war die Lösung ein Einzeiler, der auch dann funktioniert, wenn die Schlüssel keine Symbole sind, aber Sie müssen die Kriterienschlüssel in einem Array haben

criteria_array = [:choice1, :choice2]

params.select { |k,v| criteria_array.include?(k) } #=> { :choice1 => "Oh look another one",
                                                         :choice2 => "Even more strings" }

Ein anderes Beispiel

criteria_array = [1, 2, 3]

params = { 1 => "A String",
           17 => "Oh look, another one",
           25 => "Even more strings",
           49 => "But wait",
           105 => "The last string" }

params.select { |k,v| criteria_array.include?(k) } #=> { 1 => "A String"}
Jose Paez
quelle