写这篇文章最恰当的Clojure方式是什么?

时间:2010-06-23 08:38:26

标签: clojure idioms

我写了这个功能(比说明更容易显示):

(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 个答案:

答案 0 :(得分: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.

答案 1 :(得分:5)

无需编写自己的实现。 Clojure提供分区 lazy 。如果仅使用数字文字:

,也无需使用列表
 (partition 2 '(1 2 3 4 5 6)) 

答案 2 :(得分: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))'不同的原因是因为它更有效地计算XS中的M个元素而不是每次都计算整个XS集合(这是一种反对懒惰,因为我们不想走整个集合)

想象一下,每次迭代计算怪异范围内的元素会是什么样子:)

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

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

答案 3 :(得分: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,那么最好使用不使用堆栈的loop / recur:

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