【问题标题】:Reducing clojure collections with state carried through通过执行状态减少 clojure 集合
【发布时间】:2013-08-03 20:33:58
【问题描述】:

我正在学习 clojure(来自 ruby​​)并且在思考生成集合的最佳方法时遇到了一些麻烦。

我想写一个函数,它接受两个参数——向量ary和整数sum——并生成一个新的二维向量,其中每一行的总和是

这是我得到的:

(defn split-after-sum [ary sum]
  (reduce (fn [acc i]
            (let [inner-sum (+ (last acc) i)]
              (if (< inner-sum sum)
                [(conj (first acc) i) (+ i (last acc))]
                [(conj (first acc) i "X") 0])))
          [[] 0] ary))

我将传递reduce 一个 2 元素向量,以便跟踪我正在构建的集合和该行的总数。

它有点工作。我还没有弄清楚如何实际使结果成为 2D 数组,所以它只是将“X”粘贴在应该拆分的位置:

(first (split-after-sum [1 1 1 1 1 1 1 1 1] 2)) => [1 1 "X" 1 1 "X" 1 1 "X" 1 1 "X" 1]

理想的输出是:

(split-after-sum [1 1 1 1 1 1 1 1 1] 2) => [[1 1] [1 1] [1 1] [1 1] [1]]

我知道这里有一些混乱的东西,但我认为这个问题的惯用答案会很有启发性。

【问题讨论】:

    标签: clojure


    【解决方案1】:
    (defn split-after-sum [ary sum]
      (let [[acc v] (reduce (fn [[acc v s] x]
                              (let [new-s (+ s x)]
                                (if (<= new-s sum)
                                  [acc (conj v x) new-s]
                                  [(conj acc v) [x] x])))
                            [[] [] 0]
                            ary)]
        (conj acc v)))
    
    (split-after-sum [1 1 3 2 1 1 1 1 1] 3)
    ;= [[1 1] [3] [2 1] [1 1 1] [1]]
    (split-after-sum [1 1 3 2 1 1 1 1 1] 4)
    ;= [[1 1] [3] [2 1 1] [1 1 1]]
    (split-after-sum [1 1 3 2 1 1 1 1 1] 5)
    ;= [[1 1 3] [2 1 1 1] [1 1]]
    (split-after-sum [1 1 3 2 1 1 1 1 1] 6)
    ;= [[1 1 3] [2 1 1 1 1] [1]]
    

    【讨论】:

    • 这似乎与我的方法非常相似——使用reduce,通过多个参数保持状态来减少。你认为这个“好”的clojure吗?
    • 是的,Clojure 非常好。它也非常快:特别是,它比 loop / recur 方法更快。 reduce 是 Clojure 中的一个核心概念,它的性能特征已经得到——并且继续得到——大量的关注。 (我使用Criterium 进行基准测试。在这里,我对上述答案示例中使用的小向量以及(shuffle (range len)) 生成的两个向量进行了基准测试,一个长度为1024,另一个长度为16384 ; sum 的值分别为 7、17 和 173。)
    • 太棒了,感谢您对它进行基准测试。我不太担心速度,而更担心学习解决问题的正确方法,但这对我来说是一个很好的迹象,我凭直觉所做的事情实际上非常快。
    【解决方案2】:

    正如 Michał 所示,累积状态 a 可以与相关元素连接成一个元组。

    下面是一种命令式方法,其中状态保存在循环绑定中。

    (defn split-after-sum [ary sum]
      (when (seq ary)
        (loop [ary ary
               cur 0
               row []
               rows []]
          (if-let [[x & xs] ary]
            (let [nxt (+ x cur)]
              (if (<= nxt sum)
                (recur xs nxt (conj row x) rows)
                (recur xs x [x] (conj rows row))))
            (conj rows row)))))
    

    另外:参数顺序可能应该颠倒。处理序列元素的函数倾向​​于将序列作为最后一个参数。

    【讨论】:

    • 你会推荐这种方法而不是功能性方法吗?
    • 我不会称之为命令式的——相反,它是通过递归循环的,这是功能性的,如果不像使用标准组合器那样具有声明性(循环局部变量在下一次迭代中并行反弹) .有关某些基准测试结果,请参阅我对尖峰对我的回答的评论的回复。我完全同意论点顺序。
    猜你喜欢
    • 2016-03-17
    • 1970-01-01
    • 2017-11-05
    • 1970-01-01
    • 2018-10-29
    • 1970-01-01
    • 2020-03-25
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多