处理一些2015 AoC问题以学习clojure ......以下对于第40次迭代来说足够快,但在此之后又停止了很多。我与其他一些人的解决方案进行了比较,对我来说,为什么这么慢,这并不是很明显。我试图使用recur相信它与循环一样高效(并避免堆栈消耗)。我不是100%明确的一件事是,如果仅仅使用复发与使用循环复发之间存在明显差异。我对它进行了两种方式的测试,并没有发现任何差异。
(def data "3113322113")
(defn encode-string [data results count]
(let [prev (first data)
curr (second data)]
(cond (empty? data) results
(not= prev curr)
(recur (rest data) (str results count prev) 1)
:else (recur (rest data) results (inc count)))))
(count
(nth (iterate #(encode-string % "" 1) data) 40 #_50))
我所反对的解决方案的一个例子是布鲁斯·豪曼的,这非常好:
(defn count-encode [x]
(apply str
(mapcat
(juxt count first)
(partition-by identity x))))
我意识到在我的解决方案中我反复迭代非常大的字符串,但是我没有看到Bruce的速度如此快,因为虽然他没有明确地迭代,但分区可能在幕后迭代。
答案 0 :(得分:7)
您的版本正在计算类似
的内容(str "11" (str "22" (str "31" ...)))
为每两个字符构建一个全新的String对象。由于这涉及在每一步中迭代输入和输出字符串中的每个字符,因此您的操作在字符串的长度上是二次的。
您要比较的解决方案是不同的:它构建了一个懒惰的整数序列,这是一个线性时间过程。然后,它会像
那样(apply str [1 1 2 2 3 1])
与
相同(str 1 1 2 2 3 1 ...)
和str
,当使用多个参数调用时,使用StringBuilder以增量方式有效地构建结果,如果在每个中间步骤都需要完整的String对象,则该优化不可用。结果,整个过程是线性时间,而不是二次方。