Какой самый идиоматичный способ Clojure написать это? - PullRequest
12 голосов
/ 23 июня 2010

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

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

Спасибо!

Ответы [ 4 ]

21 голосов
/ 23 июня 2010

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

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

5 голосов
/ 23 июня 2010

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

  (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 голосов
/ 23 июня 2010

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

 (partition 2 '(1 2 3 4 5 6)) 
2 голосов
/ 23 июня 2010

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

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

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

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