写这个的最惯用的Clojure方式是什么?

12

我编写了以下函数(更容易展示而不是解释):

(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
你可以使用内置的分区函数,
(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])
  返回一个懒惰序列,每个列表包含n个项,间隔为step。如果未提供step,则默认为n,即分区不重叠。如果提供了填充集合,请根据需要使用其元素来完成最后一个分区的n项。如果没有足够的填充元素,则返回一个少于n项的分区。


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个元素更有效率(这有点违背了惰性,因为我们不想遍历整个集合)。

想象一下,在每次迭代中计算庞大范围中的元素会是什么样子:)

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

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

整洁,谢谢这个技巧。将循环/递归版本中的计数更改为' take n '而不是序列使10k范围内的时间从3000ms降至20ms...我必须记住next/rest返回一个序列,其中count是O(n)。 - j-g-faustus

5

不需要编写自己的实现。Clojure提供了partition函数,它是惰性的。如果你只使用数字字面量,也不需要使用list函数:

 (partition 2 '(1 2 3 4 5 6)) 

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,最好使用循环/递归,因为它不使用栈:
(defn split-loop [n coll]
  (loop [elms coll res [] ]
    (if (< (count elms) n)
      res
      (recur (next elms) (conj res (take n elms))))))

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接