2010-06-23 12 views
12

escribí esta función que hace esto (más fácil mostrar que explique):¿Cuál es la forma más idiomática de Clojure para escribir esto?

(split 2 (list 1 2 3 4 5 6))

=> ((1 2) (2 3) (3 4) (4 5) (5 6))

(defn split [n xs] 
    (if (> (count xs) (dec n)) 
     (cons (take n xs) (split n (rest xs))) 
     '())) 

entiendo que en Clojure la lista no es la única estructura primeros datos de clase. ¿Tendría sentido escribir esta estructura de datos-agnóstica? Y a pesar de ello, ¿mi implementación es la más eficiente y, en caso negativo, cómo la haré más eficiente y/o idiomática?

Gracias!

Respuesta

21

Usted puede utilizar el construido en función de partición,

(partition 2 1 (list 1 2 3 4 5 6)) 
=> ((1 2) (2 3) (3 4) (4 5) (5 6)) 

obras para cualquier secuencia.

 

clojure.core/partition 
([n coll] [n step coll] [n step pad coll]) 
    Returns a lazy sequence of lists of n items each, at offsets step 
    apart. If step is not supplied, defaults to n, i.e. the partitions 
    do not overlap. If a pad collection is supplied, use its elements as 
    necessary to complete last partition upto n items. In case there are 
    not enough padding elements, return a partition with less than n items. 
 
2

He estado usando Clojure durante aproximadamente un mes, así que probablemente no estoy calificado para designar la forma más idiomática;)

Sin embargo, su aplicación es corta y al punto (ignorando que también duplica la función incorporada partition como ya se mencionó).

La aplicación ya está bastante estructura de datos agnóstico - ya que utiliza sequence operaciones, funciona con todas las estructuras de datos estándar:

(split 2 [1 2 3 4 5 6]) 
=> ((1 2) (2 3) (3 4) (4 5) (5 6)) 

(split 2 #{1 2 3 4 5 6}) 
=> ((1 2) (2 3) (3 4) (4 5) (5 6)) 

(split 2 {1 :a 2 :b 3 :c 4 :d}) 
=> (([1 :a] [2 :b]) ([2 :b] [3 :c]) ([3 :c] [4 :d])) 

(split 2 "abcd") 
=> ((\a \b) (\b \c) (\c \d)) 

La principal limitación del uso de la recursividad llano es que está limitado por el tamaño de la pila:

(split 2 (range 10000)) 
=> java.lang.StackOverflowError 

Así que si usted está esperando entrada de tamaños muy por encima de 1k, es mejor utilizar bucle/vuelva a ocurrir, que no utiliza la pila: 01

(defn split-loop [n coll] 
    (loop [elms coll res [] ] 
    (if (< (count elms) n) 
     res 
     (recur (next elms) (conj res (take n elms)))))) 
5

No es necesario que escriba su propia implementación. Clojure proporciona partición, que es lazy. Tampoco hay necesidad de utilizar lista, si se utiliza sólo literales Número:

(partition 2 '(1 2 3 4 5 6)) 
5

Puede crear una secuencia perezosa de su versión:

(defn split [n xs] 
    (lazy-seq 
     (let [m (take n xs)] 
      (if (= n (count m)) 
      (cons m (split n (rest xs))))))) 

(la razón de la diferente condición de que su '(if (> (count xs) (dec n))' se debe a que es más eficiente contar los elementos M de XS en lugar de contar toda la colección XS cada vez (lo cual es algo contra la pereza, porque no queremos recorre toda la colección)

Imagina lo que habría sido contar los elementos en el rango monstruoso cada iteración :)

(take 10 (split 2 (range 100000000000))) 

    => ((0 1) (1 2) (2 3)...) 
+0

Limpio, gracias por la sugerencia. Hacer ese cambio único, contar la 'toma n' en lugar de la secuencia, hasta la versión repetida/recurrente redujo el tiempo de 3000ms a 20ms para un rango de 10k ...Tendré que recordar que next/rest devuelve una secuencia, donde count es O (n). –

Cuestiones relacionadas