Ruby: il modo più semplice per filtrare i tasti hash?


225

Ho un hash simile a questo:

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

E voglio un modo semplice per rifiutare tutte le chiavi che non sono scelte + int. Potrebbe essere scelta1 o scelta1 tramite scelta10. Varia.

Come posso individuare i tasti con solo la parola scelta e una cifra o cifre dopo di loro?

Bonus:

Trasforma l'hash in una stringa con tab (\ t) come delimitatore. L'ho fatto, ma ci sono volute diverse righe di codice. Di solito i maestri Rubiciani possono farlo in una o più righe.


In termini di domanda bonus, puoi chiarire come vorresti che fosse la stringa.
mikej,

Certo, l'hash di esempio sopra cederebbe: "Oh guarda, un altro \ tAnche altre stringhe \ tMa aspetta" (con non \ t alla fine della stringa, solo tra di loro)
Derek

La tua stringa di esempio è stata tagliata nel commento. Puoi usare il link modifica per modificare la tua domanda e aggiungere l'esempio lì dentro. Puoi anche pubblicare il rubino che hai creato fino ad esempio come quello che vuoi ottenere.
mikej,

2
possibile duplicato di Ruby Hash Filter
jbochi,

Risposte:


281

Modifica alla risposta originale : anche se questa è la risposta (al momento di questo commento) è la risposta selezionata, la versione originale di questa risposta è obsoleta.

Sto aggiungendo un aggiornamento qui per aiutare gli altri a evitare di essere respinti da questa risposta come ho fatto io.

Come menziona l'altra risposta, Ruby> = 2.5 ha aggiunto il Hash#slicemetodo che in precedenza era disponibile solo in Rails.

Esempio:

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

Fine della modifica. Quella che segue è la risposta originale che immagino sarà utile se sei su Ruby <2.5 senza Rails, anche se immagino che il caso sia piuttosto raro a questo punto.


Se stai usando Ruby, puoi usare il selectmetodo. Dovrai convertire la chiave da un simbolo a una stringa per fare la corrispondenza regexp. Questo ti darà un nuovo hash con solo le scelte in esso.

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

oppure puoi usare delete_ife modificare l'hash esistente, ad es

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

o se sono solo le chiavi e non i valori desiderati, allora puoi fare:

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

e questo darà al giusto una matrice di chiavi es [:choice1, :choice2, :choice3]


1
Eccellente! Una volta che lo vedo, è così semplice! Grazie mille e grazie agli altri che hanno avuto il tempo di rispondere anche loro.
Derek,

2
Oggi i simboli possono essere analizzati con espressioni regolari IIRC.
Andrew Grimm,

@Andrew, penso che tu abbia ragione. Ero su 1.8.7 mentre stavo testando la risposta a questa domanda.
mikej,

1
La controparte .selectè .reject, se ciò rende il tuo codice più idiomatico.
Joe Atzberger,

Il #slicemodo in cui funziona per me su 2.5.3 anche senza supporto attivo.
graywolf,

305

In Ruby, l'opzione Hash # select è un'opzione giusta. Se lavori con Rails, puoi usare Hash # slice e Hash # slice !. ad es. (binari 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}

12
fai attenzione alle chiavi con stringhe..h1 = {'a' => 1,: b => 2} restituirà solo {: b => 2} con h1.slice (: a,: b)
davidcollom

Soluzione eccezionale. Ho usato questo per restituire il risultato in rspec, dove volevo analizzare il valore di un hash all'interno di un hash. `` `test = {: a => 1,: b => 2, c => {: A => 1,: B => 2}} soluzione ==> test.c.slice (: B)` ` `
PriyankaK,

Sostituisci Rails con ActiveSupport e questo è perfetto;)
Geoffroy

5
questo non risponde alla domanda, poiché vuole un modo per estrarre i valori per le chiavi che sono "scelta + int". La tua soluzione può estrarre solo chiavi conosciute in anticipo.
Metakungfu,

46

Il modo più semplice è includere il gem 'activesupport'(o gem 'active_support').

Quindi, nella tua classe devi solo

require 'active_support/core_ext/hash/slice'

e chiamare

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

Credo che non valga la pena dichiarare altre funzioni che potrebbero avere dei bug, ed è meglio usare un metodo che è stato modificato negli ultimi anni.


Activerecord non è necessario per questo almeno su 2.5.
graywolf,

13

Il modo più semplice è includere la gemma 'activesupport' (o la gemma 'active_support').

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


4
Aggiungi maggiori dettagli alla tua risposta.
Mohit Jain,

13

Se lavori con le rotaie e hai le chiavi in ​​un elenco separato, puoi usare la *notazione:

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

Come indicato in altre risposte, puoi anche utilizzare slice!per modificare l'hash in atto (e restituire la chiave / i valori cancellati).


9

Questa è una riga per risolvere la domanda originale completa:

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

Ma la maggior parte delle soluzioni sopra sta risolvendo un caso in cui è necessario conoscere le chiavi in ​​anticipo, utilizzando sliceo regexp semplice.

Ecco un altro approccio che funziona per casi d'uso semplici e più complessi, che è scambiabile in fase di esecuzione

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

Ora, non solo questo consente una logica più complessa sulla corrispondenza delle chiavi o dei valori, ma è anche più facile da testare e puoi scambiare la logica di corrispondenza in fase di esecuzione.

Ex per risolvere il problema originale:

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"

1
Mi piace molto il matcher
Calin,


6

Se vuoi l'hash rimanente:

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

o se vuoi solo le chiavi:

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

A seconda di quante scelteX e irrilevantiX hai selezionato OPPURE delete_if potrebbe essere migliore. Se hai più sceltaX delete_if è meglio.
Ayckoster,

6

Metti questo in un inizializzatore

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

Quindi puoi farlo

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

o

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

5
params.select{ |k,v| k =~ /choice\d/ }.map{ |k,v| v}.join("\t")

4

Per quanto riguarda la domanda bonus:

  1. Se hai un output da questo #selectmetodo (elenco di matrici a 2 elementi):

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

    quindi prendi semplicemente questo risultato ed esegui:

    filtered_params.join("\t")
    # or if you want only values instead of pairs key-value
    filtered_params.map(&:last).join("\t")
  2. Se hai un output da questo #delete_ifmetodo (hash):

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

    poi:

    filtered_params.to_a.join("\t")
    # or
    filtered_params.values.join("\t")

Grandi soluzioni. Una domanda: filtered_params.map (&: last) .join ("\ t") è l'abbreviazione di filtered_params.map (| i | i.last) .join ("\ t")? In tal caso, cos'è "ultimo"? potrei usare &: value per ottenere il valore dell'hash?
Derek,

mapprende il blocco come argomento. Puoi creare un blocco al volo, con &:methodcostruzione, che creerebbe un blocco {|v| v.method }. Nel mio caso è &:laststato chiamato l'argomento array (array di 2 elementi). Se vuoi giocare con esso, controlla prima quale argomento stai ricevendo nel blocco (ottieni 1 argomento, non decostruirlo in più argomenti!) E prova a trovare 1 metodo (non puoi incatenare i metodi con &:method) che ti restituirebbe ciò che volere. Se è possibile, puoi utilizzare la scorciatoia, in caso contrario, hai bisogno di un blocco completo
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"}

0

Ho avuto un problema simile, nel mio caso la soluzione era una linea che funziona anche se le chiavi non sono simboli, ma è necessario disporre delle chiavi dei criteri in un array

criteria_array = [:choice1, :choice2]

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

Un altro esempio

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"}
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.