2011-10-05 9 views
15

Tengo una lista, que puede contener elementos que se puedan comparar como iguales. Me gustaría una lista similar, pero con un elemento eliminado. Así que de (: a: b: c: b: d) Me gustaría poder "eliminar" solo uno: b para obtener (: a: c: b: d).¿Qué es idiomático Clojure para "eliminar" una sola instancia de muchas en una lista?

El contexto es una mano en un juego de cartas donde dos mazos de cartas estándar están en juego, por lo que puede haber cartas duplicadas, pero todavía se juegan de a una por vez.

Tengo un código de trabajo, consulte a continuación. ¿Hay formas más idiomáticas de hacer esto en Clojure?

(defn remove-one [c left right] 
    (if (= right()) 
    left 
    (if (= c (first right)) 
     (concat (reverse left) (rest right)) 
     (remove-one c (cons (first right) left) (rest right))))) 

(defn remove-card [c cards] 
    (remove-one c() cards)) 

Estas son las respuestas Scala llegué hace un tiempo: What is an idiomatic Scala way to "remove" one element from an immutable List?

+0

Como nota al pie, miré el código fuente de la respuesta preferida a mi versión Scala de esta pregunta. Resulta que la función diff en Scala usa un hash mutable para contar el número de ocurrencias para eliminar de un conjunto múltiple. –

+0

Solo para aclarar: ¿importa qué: b cuenta como duplicado aquí? De la salida de ejemplo, 'soltó' la primera: b, pero ¿sería igual a haber descartado la segunda: b en su lugar? [abcbd -> acdb, pero ¿también sería aceptable?] – monojohnny

+0

@monojohnny, abcd es igual de bueno para mi caso de uso. –

Respuesta

23

Cómo sobre: ​​

(let [[n m] (split-with (partial not= :b) [:a :b :c :b :d])] (concat n (rest m))) 

que divide la lista en: b y luego elimina el: b y concats los dos liza.

+0

¡Pásame! Es realmente conveniente que 'split-with 'haga lo correcto cuando el elemento no se encuentra en la lista, por lo que esta solución es fácil de escribir. – mquander

+1

Muy elegante. Creo que vale la pena señalar que 'split-with 'se basa en' take-while' y 'drop-while', que son los elementos básicos para abordar problemas similares. –

+0

+1 - ¡es muy elegante! También vale la pena mencionar que esto no eliminará todas las apariciones de ': b' sino solo la primera (de hecho, pensé lo primero). Enlace directo al doc: http://clojure.github.com/clojure/clojure.core-api.html#clojure.core/split-with – kgadek

9

Normalmente resuelvo estos problemas con una función de orden superior como split-with, pero alguien ya lo ha hecho. A veces es más legible o más eficiente trabajar en un nivel más primitivo, así que aquí hay una versión mejorada del código de bucle original, usando secuencias perezosas y generalizado para tomar un predicado para eliminar en lugar de limitarse a verificaciones de igualdad:

(defn remove-once [pred coll] 
    ((fn inner [coll] 
    (lazy-seq 
     (when-let [[x & xs] (seq coll)] 
     (if (pred x) 
      xs 
      (cons x (inner xs)))))) 
    coll)) 


user> (remove-once #{:b} [:a :b :c :b :d]) 
(:a :c :b :d) 
+2

Nota: No estoy afirmando que sea más legible, pero es más eficiente y puede ser una buena práctica si está familiarizado con HOF como 'split-with 'y necesita práctica con el código recursivo" bueno ". – amalloy

+0

¡Gracias también por un buen ejemplo de usar lazy-seq! La [explicación "canónica" de la pereza] (http://clojure.org/lazy) es un poco confusa. –

1

Es sorprendente que no haya una API de alto nivel para hacer algo como esto. Aquí hay otra versión similar a @amalloy y @James que usa recurre para no apilar el desbordamiento.

(defn remove-once [x c]                                                      
    (letfn [(rmv [x c1 c2 b]                                                     
      (if-let [[v & c] (seq c1)]                                                  
       (if (and (= x v) b)                                                   
       (recur x c c2 false)                                                   
       (recur x c (cons v c2) b))                                                 
       c2))]                                                       
    (lazy-seq (reverse (rmv x c '() true)))))                                                

(remove-once :b [:a :b :c :b :d]) 
;; (:a :c :b :d) 
+2

Lo siento, pero odio esta implementación. Estás resolviendo un problema sin problemas, mi versión no arruina la pila tampoco, y no desperdicio una gran cantidad de energía llamando '' inversa''. Para empezar, la construyo de la manera correcta. – amalloy

Cuestiones relacionadas