2012-07-02 5 views
23

... Tal vez la programación imperativa con datos variables se perfora demasiado profundo en mi cerebro, pero encuentro que el código para construir vectores de datos en Clojure es prolijo, difícil de manejar y enrevesado . ¡Debe haber una mejor manera!¿La mejor manera de acumular resultados en un vector en Clojure? (El código funcional puro parece feo y prolijo)

en Ruby podría escribir código como:

results = [] 
a_collection.each do |x| 
    x.nested_collection.each do |y| 
    next if some_condition_holds 
    results << y 
    end 
end 

En Clojure, no sé de una mejor forma de hacerlo que utilizar una función recursiva, tal vez como el siguiente código (horrible):

; NEWBIE ALERT! NEWBIE ALERT! 
(loop [results [] 
     remaining a_collection] 
    (if (empty? remaining) 
     results 
     (recur 
     (loop [results results 
       nested (nested_collection (first remaining))] 
      (if (empty? nested) 
       results 
       (if (some_condition_holds) 
        (recur results (rest nested)) 
        (recur (conj results (first nested)) (rest nested))))) 
     (rest remaining)))) 

Sin datos mutables y bucles iterativos, debe usar recursividad para crear una colección. Cada una de estas funciones recursivas necesita una cláusula de guardia (empty?), etc. etc. Todo es tan repetitivo que me dan ganas de gritar.

En casos simples, map sería suficiente, pero estoy pensando en casos en los que hay múltiples niveles de anidación, y en cada nivel, puede haber condiciones que requieren omitir una iteración.

En Common Lisp podría usar la macro loop o mapcan. ¿Clojure no tiene nada como mapcan?

+0

¡GRACIAS A TODOS LOS CARTELES! ¡SUS RESPUESTAS ERAN TODAS IMPRESIONANTES! Fue difícil elegir qué respuesta aceptar. –

+0

Se ha dicho antes, pero la base de la programación funcional es la composición de funciones y el uso de funciones de orden superior, no el uso de recursión. Como regla general, la recursividad solo debe utilizarse si una solución con HOF se vuelve más compleja que una que usa recursividad. – NielsK

+5

Para explicar: utilizará toneladas de recursión, pero casi todo debería ser una recursión que alguien más ya escribió para usted, por ejemplo, en 'mapa': sería bastante raro escribir recursiones a mano. – amalloy

Respuesta

25

En orden descendente de lo bien que creo las opciones miran:

(for [x coll, 
     y (nested-collection x) 
     :when (not (some-condition-holds y))] 
    y) 

Alternativamente, si prefiere formar con funciones como map y mapcat en lugar de utilizar la sintaxis for:

(mapcat (fn [x] 
      (remove some-condition-holds 
        (nested-collection x))) 
     coll) 

Si usted está realmente interesado en ella, también se puede construir con aplicaciones de funciones parciales y composición:

(mapcat (comp (partial remove some-condition-holds) 
       nested-collection) 
     coll) 

Este tercer estilo no se lee muy bien en Clojure, aunque el código equivalente en algunos otros idiomas es muy bueno. En Haskell, por ejemplo:

coll >>= (filter (not . someConditionHolds) . nestedCollection) 
4

funciones de orden superior pueden realmente ayudar a que sea mucho más preciosa, aunque lo hace tomar un tiempo para acostumbrarse a pensar en secuencias y transformación de secuencias.

hay muchas formas de escribir esto:

user> (into [] a_colletion) 
[0 1 2 3 4 5 6 7 8 9] 

user> (vec a_colletion) 
[0 1 2 3 4 5 6 7 8 9] 

user> (for [x a_colletion :when (even? x)] x) 
(0 2 4 6 8) 

un ejemplo más complejo podría ser algo como esto:

(flatten (for [x (map extract-from-nested-collection a_collection) 
       :when (test-conditions? x)] 
      x)) 

hacer una colección anidada

user> (def a_collection (map #(reductions + (range %)) (range 1 5))) 
#'user/a_collection 
user> a_collection 
((0) (0 1) (0 1 3) (0 1 3 6)) 

recuperar una anidada colección de cada elemento de a_collection y omita algunos de ellos:

user> (map #(filter pos? %) a_collection) 
(() (1) (1 3) (1 3 6)) 

anexar las colecciones anidados juntos

user> (flatten (map #(filter pos? %) a_collection)) 
(1 1 3 1 3 6) 

filtro alguna cosa más grande que 3 de la colección aplanado y luego cuadrados cada una de ellas

user> (for [x (flatten (map #(filter pos? %) a_collection)) 
       :when (<= x 3)] 
      (* x x)) 
(1 1 9 1 9) 
user> 
+0

'into' sería bueno si solo quisiera agregar' a_collection' a un vector; pero en el ejemplo, necesito recuperar una colección anidada de cada elemento de 'a_collection' y anexar las colecciones anidadas. Además, necesito filtrar los elementos que se acumulan en el vector. –

+0

puede separar cada uno de estos en su propia acción. Agregaré un ejemplo pronto –

+0

Gracias por los ejemplos ... Me gustan. Pero creo que '(mapcat ...)' como lo demuestra @Hendekagon es mejor que '(flatten (map ...))'. –

7
(mapcat (fn [y] (filter condition y)) x) 
7

Otros ya han proporcionado respuestas con respecto a la forma de resolver el problema especificado utilizando conceptos de PF como el uso de funciones de orden superior. Si analiza su proceso de pensamiento que conduce a su código actual y lo compara con las soluciones de PF que otras personas le han proporcionado, encontrará que cada vez que piense en "tener una variable para almacenar el resultado procesado", se traducirá en un imperativo O paso a paso, tipo de solución y, por lo tanto, su código Clojure es, en su mayoría, imprescindible ya que pensó en almacenar el resultado como una "variable de vector". Este tipo de pensamiento no le permitirá aplicar conceptos de FP que se basan en "evaluación de expresión" y "resolver problema por composición"

+1

para jugar al abogado del diablo, y por ingenuidad, ¿cómo difiere la composición funcional de "paso a paso", aparte de que los argumentos de cada función son vars inmutables? – Hendekagon

+0

Eventualmente todo se ejecuta como "paso a paso" pero la diferencia es cómo piensas acerca de la solución. Ej: multiplicar es el resultado de la composición del complemento y, de manera similar, todo en este mundo se construye a partir de la composición de elementos primitivos y el pegamento para componer 2 cosas es también otra cosa primitiva o compuesta. – Ankur

2

amalloy's answer es probablemente mejor si desea seguir un estilo funcional idiomático y producir un perezoso secuencia.

Si usted está realmente interesado en la construcción imperativamente un vector (en lugar de una secuencia perezoso), probablemente lo haría mediante un átomo y doseq de la siguiente manera:

(let [v (atom [])] 
    (doseq [x (range 5) 
      y (range 5)] 
    (if (> y x) 
     (swap! v conj (str x y)))) 
    @v) 

=> ["01" "02" "03" "04" "12" "13" "14" "23" "24" "34"] 

Como se puede ver, esto termina muy similar en estructura al código de Ruby.

También es posible hacer esto usando reducir, sin embargo, esto es más adecuado cuando solo hay una secuencia de entrada, p.:

(reduce 
    (fn [v x] 
    (if (even? x) 
     (conj v x) 
     v)) 
    [] 
    (range 20)) 

=> [0 2 4 6 8 10 12 14 16 18] 
+0

Creo que usar 'reduce' con un vector vacío como el acumulador tendría más sentido. – ponzao

+0

@ponzao - Acabo de agregar un ejemplo de reducción a medida que sucede :-) pero reducir no es tan adecuado para iterar más de dos colecciones simultáneamente porque te obliga a construir una secuencia intermedia de [x y] pares. También podría usar la solución de amalloy en ese punto. – mikera

+0

Sí, está bien, mi mal, no leí correctamente la pregunta original :) – ponzao

Cuestiones relacionadas