Ruby: ¿La Forma más Fácil de Filtrar las Claves Hash?


Tengo un hash que se ve algo como esto:

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

Y quiero una forma sencilla de rechazar todas las claves que no son choice+int. Podría ser choice1, o choice1 a choice10. Varía.

¿Cómo singularizo las claves con solo la elección de palabra y un dígito o dígitos después de ellas?

Bono:

Convierte el hash en una cadena con tab (\t) como delimitador. Hice esto, pero tomó varias líneas de código. Por lo general, los maestros rubicianos pueden hacerlo en uno o dos alinear.

 166
Author: Derek, 2011-09-15

12 answers

NOTA: Si estás usando Rails, puedes usar Hash.slice como indica lfx_cool en la respuesta a continuación.

Si estás usando Ruby, puedes usar el método select. Necesitarás convertir la clave de un Símbolo a una Cadena para hacer la coincidencia de expresiones regulares. Esto te dará un nuevo Hash con solo las opciones en él.

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

O puede usar delete_if y modificar el Hash existente, por ejemplo,

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

O si son solo las claves y no los valores que desea, entonces puede hacer:

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

Y esto le dará al solo una matriz de las claves, por ejemplo, [:choice1, :choice2, :choice3]

 199
Author: mikej,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-05-04 12:29:08

En Ruby, el Hash#select es una opción correcta. Si trabajas con Rails, puedes usar Hash # slice y Hash#slice!. por ejemplo (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}
 289
Author: lfx_cool,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-05-22 04:08:29

La forma más fácil es incluir el gem 'activesupport' (o gem 'active_support').

Entonces, en tu clase solo necesitas

require 'active_support/core_ext/hash/slice'

Y llamar

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

Creo que no vale la pena declarar otras funciones que pueden tener errores, y es mejor usar un método que se ha ajustado durante los últimos años.

 45
Author: Nuno Costa,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-06-16 10:11:25

La forma más fácil es incluir la gema 'activesupport' (o gema 'active_support').

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

 11
Author: 翟英昌,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-04-27 07:27:48

Esta es una línea para resolver la pregunta original completa:

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

Pero la mayoría de las soluciones anteriores están resolviendo un caso en el que necesita conocer las claves con anticipación, usando slice o regexp simple.

Aquí hay otro enfoque que funciona para casos de uso simples y más complejos, que se puede intercambiar en tiempo de ejecución

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

Ahora no solo esto permite una lógica más compleja sobre la coincidencia de las claves o los valores, sino que también es más fácil de probar, y puede intercambiar la coincidencia lógica en tiempo de ejecución.

Ex para resolver el problema original:

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"
 9
Author: metakungfu,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-12-07 22:22:04

Si trabajas con rails y tienes las claves en una lista separada, puedes usar la notación *:

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

Como se indica en otras respuestas, también puede usar slice! para modificar el hash en su lugar (y devolver la clave/los valores borrados).

 8
Author: Robert,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-01-30 16:21:05

Pon esto en un inicializador

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

Entonces puedes hacer

{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"}
 6
Author: montrealmike,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-04-10 14:32:50

Con Hash::select:

params = params.select { |key, value| /^choice\d+$/.match(key.to_s) }
 5
Author: arnaud576875,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2011-09-15 11:57:31

Si quieres el hash restante:

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

O si solo quieres las claves:

params.keys.delete_if {|k| ! k.match(/choice[0-9]+/)}
 5
Author: ayckoster,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2011-09-15 11:59:55
params.select{ |k,v| k =~ /choice\d/ }.map{ |k,v| v}.join("\t")
 5
Author: Puhlze,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-05-22 04:31:27

En cuanto a la pregunta de bonificación:

  1. Si tiene salida de #select método como este (lista de matrices de 2 elementos):

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

    Entonces simplemente toma este resultado y ejecuta:

    filtered_params.join("\t")
    # or if you want only values instead of pairs key-value
    filtered_params.map(&:last).join("\t")
    
  2. Si tienes salida de #delete_if método como este (hash):

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

    Entonces:

    filtered_params.to_a.join("\t")
    # or
    filtered_params.values.join("\t")
    
 4
Author: MBO,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2011-09-15 12:12:53
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"}
 2
Author: Arup Rakshit,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-02-17 14:39:46