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

Спасибо!

12
задан Tyler 23 June 2010 в 08:38
поделиться

4 ответа

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

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

21
ответ дан 2 December 2019 в 04:33
поделиться

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

  (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)...)
5
ответ дан 2 December 2019 в 04:33
поделиться

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

 (partition 2 '(1 2 3 4 5 6)) 
5
ответ дан 2 December 2019 в 04:33
поделиться

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

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

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

(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

Поэтому, если вы ожидаете, что размер входных данных будет намного больше 1 КБ, лучше использовать цикл / повтор, который не использует стек:

(defn split-loop [n coll]
  (loop [elms coll res [] ]
    (if (< (count elms) n)
      res
      (recur (next elms) (conj res (take n elms))))))
2
ответ дан 2 December 2019 в 04:33
поделиться
Другие вопросы по тегам:

Похожие вопросы: