Подтвердить что ты не робот

Какой самый идиоматический способ 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 список не является единственной структурой данных первого класса. Будет ли смысл писать эту структуру данных - агностик? И независимо от того, является ли моя реализация наиболее эффективной, а если нет, как я могу сделать ее более эффективной и/или идиоматической?

Спасибо!

4b9b3361

Ответ 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))))))