【问题标题】:What's the most idiomatic Clojure way to write this?写这个的最惯用的 Clojure 方式是什么?
【发布时间】:2011-03-07 05:16:14
【问题描述】:

我写了这个函数来做这个(展示比解释更容易):

(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 中,列表并不是唯一的一流数据结构。编写这种与数据结构无关的数据有意义吗?无论如何,我的实现是最有效的吗?如果不是,我将如何使其更高效和/或惯用?

谢谢!

【问题讨论】:

    标签: clojure idioms


    【解决方案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,它是 lazy。如果您只使用数字文字,也无需使用 list

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

        (条件与您的 '(if (> (count xs) (dec n))' 不同的原因是因为从 XS 中计算出 M 个元素而不是每次都计算整个 XS 集合更有效(有点反对懒惰,因为我们不想遍历整个集合)

        想象一下每次迭代都在巨大范围内计算元素会是什么样子:)

          (take 10 (split 2 (range 100000000000)))
        
            => ((0 1) (1 2) (2 3)...)
        

        【讨论】:

        • 很好,感谢您的提示。对循环/递归版本进行单一更改 - 计算 'take n' 而不是序列 - 将 10k 范围的时间从 3000 毫秒减少到 20 毫秒......我必须记住下一个/休息返回一个序列,其中计数为 O(n)。
        【解决方案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,则最好使用不使用堆栈的循环/递归:

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

        【讨论】:

          猜你喜欢
          • 2013-12-16
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2011-08-13
          • 2013-10-05
          • 1970-01-01
          • 1970-01-01
          • 2013-06-25
          相关资源
          最近更新 更多