2012-03-02 47 views
25

Estoy intentando obtener un subconjunto de claves para cada hash en una matriz.¿Cómo puedo filtrar una matriz de valores hash para obtener solo las claves en otra matriz?

Los hashes son en realidad mucho más grande, pero pensé que esto es más fácil de entender:

[ 
    { 
    id:2, 
    start: "3:30", 
    break: 30, 
    num_attendees: 14 
    }, 
    { 
    id: 3, 
    start: "3: 40", 
    break: 40, 
    num_attendees: 4 
    }, 
    { 
    id: 4, 
    start: "4: 40", 
    break: 10, 
    num_attendees: 40 
    } 
] 

Quiero obtener sólo los id y start valores.

que he probado:

return_keys = ['id','start'] 
return_array = events.select{|key,val| key.to_s.in? return_keys} 

pero esto devuelve una matriz vacía.

Respuesta

40

Esto debería hacer lo que quiera:

events.map do |hash| 
    hash.select do |key, value| 
    [:id, :start].include? key 
    end 
end 

potencialmente más rápido (pero algo menos bonito) solución:

events.map do |hash| 
    { id: hash[:id], start: hash[:start] } 
end 

Si necesita return_keys ser dinámico:

return_keys = [:id, :start] 
events.map do |hash| 
    {}.tap do |new_hash| 
    return_keys.each do |key| 
     new_hash[key] = hash[key] 
    end 
    end 
end 

Tenga en cuenta que, en su código, select selecciona elementos del array, ya que así es como lo llamaste, pero no cambia los valores hash contenidos en la matriz.

Si usted está preocupado por el rendimiento, he Benchmarked todas las soluciones aquí enumeradas (code):

   user  system  total  real 
amarshall 1 0.140000 0.000000 0.140000 ( 0.140316) 
amarshall 2 0.060000 0.000000 0.060000 ( 0.066409) 
amarshall 3 0.100000 0.000000 0.100000 ( 0.101469) 
tadman 1  0.140000 0.010000 0.150000 ( 0.145489) 
tadman 2  0.110000 0.000000 0.110000 ( 0.111838) 
mu   0.130000 0.000000 0.130000 ( 0.128688) 
+0

Para n claves en 'events' y teclas M en cada uno de hash, y teclas P de la matriz interior, esto se realiza en ** O (MNP) ** velocidad, lo que podría ser paralizantemente lento. – tadman

+1

@tadman Ver la respuesta actualizada con la solución O (N). –

+0

@tadman Sin embargo, supongo que es realmente O (NP)? No creo que haya nada más rápido que eso. Asumiendo que P es muy pequeño, no debería afectar la complejidad del tiempo. –

2

Una mejor solución es utilizar un hash como su índice en lugar de hacer una serie lineal de búsqueda para cada tecla:

events = [{id:2, start:"3:30",break:30,num_attendees:14},{id:3, start:"3:40",break:40,num_attendees:4},{id:4, start:"4:40",break:10,num_attendees:40}] 

return_keys = [ :id, :start ] 

# Compute a quick hash to extract the right values: { key => true } 
key_index = Hash[return_keys.collect { |key| [ key, true ] }] 

return_array = events.collect do |event| 
    event.select do |key, value| 
    key_index[key] 
    end 
end 

# => [{:id=>2, :start=>"3:30"}, {:id=>3, :start=>"3:40"}, {:id=>4, :start=>"4:40"}] 

he ajustado esta opción para utilizar símbolos como los nombres clave para que coincida con su definición de events.

Esto se puede mejorar aún más mediante el uso de la return_keys como conductor directo:

events = [{id:2, start:"3:30",break:30,num_attendees:14},{id:3, start:"3:40",break:40,num_attendees:4},{id:4, start:"4:40",break:10,num_attendees:40}] 

return_keys = [ :id, :start ] 

return_array = events.collect do |event| 
    Hash[ 
    return_keys.collect do |key| 
     [ key, event[key] ] 
    end 
    ] 
end 

El resultado es el mismo. Si el subconjunto que está extrayendo tiende a ser mucho más pequeño que el original, este podría ser el mejor enfoque.

+1

En caso de que tenga curiosidad, analicé todas las soluciones aquí y publiqué los resultados en mi respuesta ':)'. –

28

Si le sucede a estar utilizando los carriles (o no les importa tirar en la totalidad o parte de ActiveSupport) entonces se podría utilizar Hash#slice:

return_array = events.map { |h| h.slice(:id, :start) } 

Hash#slice hace algún trabajo extra bajo las sábanas, pero es probable que sea lo suficientemente rápido como para que no lo note por pequeños hashes y la claridad es bastante agradable.

+5

En realidad, es necesario requerir ' 'active_support/core_ext'' si no estás en Rails. Las extensiones principales deben cargarse explícitamente, por lo que solo 'require 'active_support'' no funciona. (Digo esto porque esto último es lo que la mayoría consideraría "tirando en todos ActiveSupport".) –

+0

En cuanto a la velocidad, ver mi respuesta para los puntos de referencia ':)'. –

0

Considerando que la eficiencia parece ser una preocupación, sugeriría lo siguiente.

Código

require 'set' 

def keep_keys(arr, keeper_keys) 
    keepers = keeper_keys.to_set 
    arr.map { |h| h.select { |k,_| keepers.include?(k) } } 
end 

Esto utiliza Hash#select, que, a diferencia de Enumerable#select, devuelve un hash. He convertido keeper_keys en un conjunto de búsquedas rápidas.

Ejemplos

arr = [{ id:2, start: "3:30", break: 30 }, 
     { id: 3, break: 40, num_attendees: 4 }, 
     { break: 10, num_attendees: 40 }] 

keep_keys arr, [:id, :start] 
    #=> [{:id=>2, :start=>"3:30"}, {:id=>3}, {}] 
keep_keys arr, [:start, :break] 
    #=> [{:start=>"3:30", :break=>30}, {:break=>40}, {:break=>10}] 
keep_keys arr, [:id, :start, :cat] 
    #=> [{:id=>2, :start=>"3:30"}, {:id=>3}, {}] 
keep_keys arr, [:start] 
    #=> [{:start=>"3:30"}, {}, {}] 
keep_keys arr, [:cat, :dog] 
Cuestiones relacionadas