clojure中的最大子阵算法

clojure中的最大子阵算法,clojure,Clojure,kanade的算法解决了这个问题。我试图学习clojure,因此我提出了以下实现: (defn max-subarray [xs] (last (reduce (fn [[here sofar] x] (let [new-here (max 0 (+ here x))] [new-here (max new-here sofar)])) [0 0] xs))) (= (max-subarray-orig xs)

kanade的算法解决了这个问题。我试图学习clojure,因此我提出了以下实现:

(defn max-subarray [xs]
  (last
    (reduce
      (fn [[here sofar] x]
        (let [new-here (max 0 (+ here x))]
          [new-here (max new-here sofar)]))
      [0 0]
      xs)))
(= (max-subarray-orig xs) (max-subarray-prim xs))
;= true

这似乎很冗长在clojure中是否有更干净的方法来实现此算法?

在这里,它使用循环和重现来更接近地模拟wikipedia页面中的示例

user> (defn max-subarray [xs]                                           
        (loop [here 0 sofar 0 ar xs]                                    
              (if (not (empty? ar))                                     
                  (let [x (first ar) new-here (max 0 (+ here x))]       
                    (recur new-here (max new-here sofar) (rest ar)))    
                sofar)))                                                
#'user/max-subarray                                                     
user> (max-subarray [0 -1 1 2 -4 3])                                    
3

有些人可能会觉得这更容易理解,其他人更喜欢reduce或map。

正如我在一篇关于这个问题的评论中所说,我相信OP的方法是最佳的。这就给出了一个完全一般的问题,其中输入是任意数的序列表

但是,如果增加了输入应该是长(或双精度;其他原语也可以,只要我们不将整数与浮点数混合)的集合的要求,则可以通过利用原语算术使基于
循环的解决方案大大加快速度:

(defn max-subarray-prim [xs]
  (loop [xs (seq xs) here 0 so-far 0]
    (if xs
      (let [x (long (first xs))
            new-here (max 0 (+ here x))]
        (recur (next xs) new-here (max new-here so-far)))
      so-far)))
这实际上在我看来是相当可读的,尽管我确实更喜欢
reduce
,在没有特别理由使用
loop
/
recur
的地方。现在的希望是
循环
在这里保持
的能力
和到目前为止的
在整个循环执行过程中保持不绑定的能力将在性能方面产生足够的差异

为了对此进行基准测试,我从-50000,…,49999范围内生成了一个100000个随机整数的向量:

(def xs (vec (repeatedly 100000 #(- (rand-int 100000) 50000))))
健全性检查(
max subarray orig
指OP的实现):

基准:

(do (c/bench (max-subarray-orig xs))
    (flush)
    (c/bench (max-subarray-prim xs)))
WARNING: Final GC required 3.8238570080506156 % of runtime
Evaluation count : 11460 in 60 samples of 191 calls.
             Execution time mean : 5.295551 ms
    Execution time std-deviation : 97.329399 µs
   Execution time lower quantile : 5.106146 ms ( 2.5%)
   Execution time upper quantile : 5.456003 ms (97.5%)
                   Overhead used : 2.038603 ns
Evaluation count : 28560 in 60 samples of 476 calls.
             Execution time mean : 2.121256 ms
    Execution time std-deviation : 42.014943 µs
   Execution time lower quantile : 2.045558 ms ( 2.5%)
   Execution time upper quantile : 2.206587 ms (97.5%)
                   Overhead used : 2.038603 ns

Found 5 outliers in 60 samples (8.3333 %)
    low-severe   1 (1.6667 %)
    low-mild     4 (6.6667 %)
 Variance from outliers : 7.8724 % Variance is slightly inflated by outliers

因此,每次通话时间从~5.29毫秒增加到~2.12毫秒。

我觉得这是一个不错的选择,并不明显需要“改进”。它可以用map和loop/recur表示,尽管它们同样好。另一个解决方案——我同意@ArthurUlfeldt的评论,这是非常好的Clojure。我个人会用
peek
来代替
last
peek
在向量(O(1)方面比
last
的O(n)更有效,并且在意图方面对我来说同样清晰;如果你发现
last
更清晰,那么它在大小为2的向量上是完全正确的。@edbond我承认,在盯着rosettacode impl并花了一些时间阅读fn的文档之后,我不知道它是如何工作的:)据我所知,它实际上比较了每个子序列的总和。两个注释:
(seq xs)
(not(empty?xs))
(实际上
empty?
上的docstring是这样说的),因为
empty?
被实现为
(not(seq xs))
;此外,您可以在开始时(在绑定向量中)对输入调用
seq
,然后在
recur
中使用
next
,只需使用
ar
作为条件(因为
next
的行为类似于
seq
rest
组成,但可能更有效)不用说,对于一个实际的基元数组,我们可以做得更好。