2010-06-23 19 views
12

ho scritto questa funzione che fa questo (più facile mostrare che spiegare):Qual è il modo Clojure più idiomatico di scrivere questo?

(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))) 
     '())) 

Capisco che in Clojure l'elenco non è l'unica struttura prima dei dati di classe. Avrebbe senso scrivere questa struttura dati-agnostica? E a prescindere, la mia implementazione è la più efficiente e se non la stessa, come la renderei più efficiente e/o idiomatica?

Grazie!

risposta

21

È possibile utilizzare il built in funzione di partizione,

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

opere per ogni sequenza.

 

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

Sto usando Clojure per circa un mese, quindi non sono probabilmente qualificato per nominare il modo più idiomatica;)

Ma l'implementazione è breve e al punto (ignorando che duplica anche la funzione integrata partition come già accennato).

L'attuazione è già abbastanza struttura dati agnostico - quanto utilizza sequence operazioni, funziona con tutte le strutture di dati standard:

(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)) 

Il limite principale di utilizzare ricorsione chiaro è che si sono limitati dalle dimensioni dello stack:

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

Quindi, se vi aspettate di ingresso dimensioni molto al di sopra 1k, è meglio usare loop/ricorrono, che non usa la pila:

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

Non è necessario scrivere la propria implementazione. Clojure fornisce la divisione , ovvero pigro. Anche senza bisogno di usare lista, se si utilizza solo numerici letterali:

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

È possibile creare una sequenza pigro dalla vostra versione:

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

(la ragione per la condizione diversa da quella '(if (> (count xs) (dec n))' è perché è più efficiente contare gli elementi M fuori da XS invece di contare l'intera collezione XS ogni volta (il che è un po 'contro la pigrizia, perché non vogliamo a piedi l'intera collezione)

Immagina come sarebbe stato il conteggio degli elementi nella gamma mostruosa ogni iterazione :)

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

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

Neat, grazie per il suggerimento. Fare quel singolo cambiamento - contando il "take n" piuttosto che la sequenza - alla versione loop/recur ha ridotto il tempo da 3000ms a 20ms per un range di 10k ...Dovrò ricordare che next/rest restituisce una sequenza, dove count è O (n). –

Problemi correlati