Ruby: le moyen le plus simple de filtrer les clés de hachage?


225

J'ai un hachage qui ressemble à ceci:

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

Et je veux un moyen simple de rejeter toutes les clés qui ne sont pas choix + int. Cela peut être choix1 ou choix1 à choix10. Cela varie.

Comment puis-je distinguer les touches avec juste le choix du mot et un ou plusieurs chiffres après eux?

Prime:

Transformez le hachage en une chaîne avec tabulation (\ t) comme délimiteur. Je l'ai fait, mais il a fallu plusieurs lignes de code. Habituellement, les maîtres Rubiciens peuvent le faire en une ou deux lignes.


En ce qui concerne la question bonus, pouvez-vous préciser à quoi vous aimeriez que la chaîne ressemble.
mikej

Bien sûr, l'exemple de hachage ci-dessus donnerait: "Oh regardez, un autre \ tEven encore plus de chaînes \ tMais attendez" (avec pas \ t à la fin de la chaîne, seulement entre eux)
Derek

Votre exemple de chaîne a été coupé dans le commentaire. Vous pouvez utiliser le lien de modification pour modifier votre question et y ajouter l'exemple. Vous pouvez également publier le rubis que vous avez trouvé en tant qu'exemple de ce que vous souhaitez réaliser.
mikej

2
doublon possible de Ruby Hash Filter
jbochi

Réponses:


281

Modifier la réponse d'origine : même si cette réponse (au moment de ce commentaire) est la réponse sélectionnée, la version originale de cette réponse est obsolète.

J'ajoute une mise à jour ici pour aider les autres à éviter de se laisser distraire par cette réponse comme je l'ai fait.

Comme le mentionne l'autre réponse, Ruby> = 2.5 a ajouté la Hash#sliceméthode qui n'était auparavant disponible que dans Rails.

Exemple:

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

Fin du montage. Ce qui suit est la réponse originale qui, je suppose, sera utile si vous êtes sur Ruby <2.5 sans Rails, bien que j'imagine que ce cas est assez rare à ce stade.


Si vous utilisez Ruby, vous pouvez utiliser la selectméthode. Vous devrez convertir la clé d'un symbole en une chaîne pour faire la correspondance d'expression régulière. Cela vous donnera un nouveau hachage avec juste les choix.

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

ou vous pouvez utiliser delete_ifet modifier le Hash existant par exemple

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

ou si ce ne sont que les clés et non les valeurs que vous souhaitez, vous pouvez faire:

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

et cela donnera le juste un tableau des clés par exemple [:choice1, :choice2, :choice3]


1
Excellent! Une fois que je le vois, c'est si simple! Merci beaucoup, et merci aux autres qui ont pris le temps de répondre également.
Derek

2
Les symboles peuvent être analysés avec des expressions régulières de nos jours IIRC.
Andrew Grimm

@Andrew, je pense que tu as raison. J'étais le 1.8.7 lorsque je testais la réponse à cette question.
mikej

1
La contrepartie .selectest .reject, si cela rend votre code plus idiomatique.
Joe Atzberger

La #slicevoie fonctionne pour moi sur 2.5.3 même sans support actif.
graywolf

305

Dans Ruby, la sélection Hash # est une bonne option. Si vous travaillez avec Rails, vous pouvez utiliser Hash # slice et Hash # slice !. par exemple (rails 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
attention aux clés stringifiées..h1 = {'a' => 1,: b => 2} ne renverra que {: b => 2} avec h1.slice (: a,: b)
davidcollom

Super solution. Je l'ai utilisé pour renvoyer le résultat dans rspec, où je voulais analyser une valeur de hachage dans un hachage. `` `test = {: a => 1,: b => 2, c => {: A => 1,: B => 2}} solution ==> test.c.slice (: B)` ` `
PriyankaK

Remplacez Rails par ActiveSupport et c'est parfait;)
Geoffroy

5
cela ne répond pas à la question, car il veut un moyen d'extraire les valeurs des clés qui sont "choix + int". Votre solution ne peut extraire que les clés connues à l'avance.
metakungfu

46

Le moyen le plus simple consiste à inclure le gem 'activesupport'(ou gem 'active_support').

Ensuite, dans votre classe, il vous suffit de

require 'active_support/core_ext/hash/slice'

et appeler

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

Je crois que cela ne vaut pas la peine de déclarer d'autres fonctions qui peuvent avoir des bogues, et il vaut mieux utiliser une méthode qui a été modifiée au cours des dernières années.


Activerecord n'est pas nécessaire pour cela au moins sur 2.5.
graywolf

13

Le moyen le plus simple consiste à inclure le joyau «actifsupport» (ou le joyau «active_support»).

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


4
Veuillez ajouter plus de détails à votre réponse.
Mohit Jain

13

Si vous travaillez avec des rails et que vous avez les clés dans une liste séparée, vous pouvez utiliser la *notation:

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

Comme d'autres réponses l'ont indiqué, vous pouvez également utiliser slice!pour modifier le hachage en place (et renvoyer la clé / les valeurs effacées).


9

Ceci est une ligne pour résoudre la question d'origine complète:

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

Mais la plupart des solutions ci-dessus résolvent un cas où vous devez connaître les clés à l'avance, en utilisant sliceou une expression rationnelle simple.

Voici une autre approche qui fonctionne pour des cas d'utilisation simples et plus complexes, qui est permutable à l'exécution

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

Maintenant, non seulement cela permet une logique plus complexe lors de la correspondance des clés ou des valeurs, mais il est également plus facile à tester et vous pouvez échanger la logique correspondante au moment de l'exécution.

Ex pour résoudre le problème d'origine:

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
J'aime vraiment le match
Calin


6

Si vous voulez le hachage restant:

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

ou si vous voulez juste les clés:

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

Selon le nombre de choiceX et de non-pertinenceX que vous avez sélectionné OU delete_if peut être préférable. Si vous avez plus de choix, X delete_if est préférable.
ayckoster

6

Mettez ceci dans un initialiseur

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

Ensuite, vous pouvez faire

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

ou

{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

Quant à la question bonus:

  1. Si vous avez sorti une #selectméthode comme celle-ci (liste des tableaux à 2 éléments):

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

    puis prenez simplement ce résultat et exécutez:

    filtered_params.join("\t")
    # or if you want only values instead of pairs key-value
    filtered_params.map(&:last).join("\t")
  2. Si vous avez sorti une #delete_ifméthode comme celle-ci (hachage):

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

    puis:

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

Excellentes solutions. Une question: Filtré_params.map (&: dernier) .join ("\ t") est l'abréviation de filtré_params.map (| i | i.last) .join ("\ t")? Si oui, qu'est-ce qui est «dernier»? puis-je utiliser &: value pour obtenir la valeur du hachage?
Derek

mapprend le bloc comme argument. Vous pouvez créer un bloc à la volée, avec une &:methodconstruction, ce qui créerait un bloc {|v| v.method }. Dans mon cas, &:laston a appelé l'argument tableau (tableau à 2 éléments). Si vous voulez jouer avec lui, vérifiez d'abord quel argument recevez-vous dans le bloc (obtenez 1 argument, ne le déconstruisez pas en plusieurs arguments!) Et essayez de trouver 1 méthode (vous ne pouvez pas enchaîner les méthodes avec &:method) qui vous retournerait ce que vous vouloir. Si c'est possible, vous pouvez utiliser un raccourci, sinon, vous avez besoin d'un bloc complet
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

J'ai eu un problème similaire, dans mon cas, la solution était une doublure qui fonctionne même si les clés ne sont pas des symboles, mais vous devez avoir les clés de critères dans un tableau

criteria_array = [:choice1, :choice2]

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

Un autre exemple

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"}
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.