2010-06-23 2 views
12

Я написал эту функцию, которая делает это (проще показать, чем объяснить):Какой самый идиоматический способ Clojure написать это?

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

Я понимаю, что в Clojure список не только первая структура данных класса. Будет ли смысл писать эту структуру данных - агностик? И независимо от того, является ли моя реализация наиболее эффективной, а если нет, как я могу сделать ее более эффективной и/или идиоматичной?

Спасибо!

ответ

21

Вы можете использовать встроенную функцию разбиения,

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

работает для любой последовательности.

 

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

Я использую Clojure в течение месяца, так что я, вероятно, не имеет права назначать наиболее идиоматических способ;)

Но ваша реализация коротка и к точке (игнорируя, что оно также дублирует встроенную функцию partition, как уже упоминалось).

Реализация уже довольно структура данных агностика - так как он использует sequence операций, он работает со всеми стандартными структурами данных:

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

Основным ограничением при использовании обычной рекурсии является то, что вы ограничены по размеру стека:

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

Так что, если вы ждете ввода размеров намного выше 1k, то лучше использовать цикл/повторялись, который не использует стек:

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

Не нужно писать собственную реализацию. Clojure предоставляет раздел, что составляет ленивый. Также нет необходимости использовать список, если вы используете только Number литералов:

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

Вы можете создать ленивую последовательность из версии:

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

(причина различных условий, чем ваш '(if (> (count xs) (dec n))' - это потому, что более эффективно подсчитывать M элементов из XS, а не подсчитывать всю коллекцию XS каждый раз (что отчасти против ленивости, потому что мы не хотим прогулка по всей коллекции)

Представьте себе, как это было бы считать подсчет элементов в чудовищном диапазоне на каждой итерации :)

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

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

Уход, спасибо за подсказку. Выполнение этого единственного изменения - подсчет «взять n», а не последовательность - в версию loop/recur сократило время от 3000 мс до 20 мс для диапазона 10 тыс. ...Я должен буду помнить, что next/rest возвращает последовательность, где count - O (n). –

Смежные вопросы