Какой самый идиоматический способ 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 список не является единственной структурой данных первого класса. Будет ли смысл писать эту структуру данных - агностик? И независимо от того, является ли моя реализация наиболее эффективной, а если нет, как я могу сделать ее более эффективной и/или идиоматической?
Спасибо!
Ответы
Ответ 1
Вы можете использовать встроенную функцию раздела,
(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 2 '(1 2 3 4 5 6))
Ответ 3
Вы можете создать ленивую последовательность из вашей версии:
(defn split [n xs]
(lazy-seq
(let [m (take n xs)]
(if (= n (count m))
(cons m (split n (rest xs)))))))
(причина для другого условия, кроме вашего "(если ( > (count xs) (dec n))", потому что его более эффективно подсчитывать M элементов из XS, а не подсчитывать всю коллекцию XS каждый раз (что является своего рода против лени, потому что мы не хотим ходить по всей коллекции)
Представьте, что это было бы как подсчет элементов в чудовищном диапазоне на каждой итерации:)
(take 10 (split 2 (range 100000000000)))
=> ((0 1) (1 2) (2 3)...)
Ответ 4
Я использую 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, лучше использовать loop/recur, который не использует стек:
(defn split-loop [n coll]
(loop [elms coll res [] ]
(if (< (count elms) n)
res
(recur (next elms) (conj res (take n elms))))))