我开始学习Clojure,并决定在HackerRank上进行一些项目是实现此目标的好方法。我发现我的Clojure解决方案非常慢。我以为那是因为我仍然必须进行必要的思考,或者只是对Clojure的运作方式不了解。我为之撰写解决方案的最新问题是《归零零》。这是我的Java代码
import java.io.BufferedReader;
import java.io.InputStreamReader;
public class Solution {
private static final int MAX_NUMBER = 1000000;
private static final BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
public static int[] precompute() {
int[] values = new int[MAX_NUMBER];
values[0] = 0;
values[1] = 1;
for (int i = 1; i < MAX_NUMBER; i += 1) {
if ((values[i] == 0) || (values[i] > (values[i - 1] + 1))) {
values[i] = (values[i - 1] + 1);
}
for (int j = 1; j <= i && (i * j) < MAX_NUMBER; j += 1) {
int mult = i * j;
if ((values[mult] == 0) || (values[mult] > (values[i] + 1))) {
values[mult] = values[i] + 1;
}
}
}
return values;
}
public static void main(String[] args) throws Exception {
int numQueries = Integer.parseInt(reader.readLine());
int[] values = Solution.precompute();
for (int loop = 0; loop < numQueries; loop += 1) {
int query = Integer.parseInt(reader.readLine());
System.out.println(values[query]);
}
}
}
我的Clojure实现是
(def MAX-NUMBER 1000000)
(defn set-i [out i]
(cond
(= 0 i) (assoc out i 0)
(= 1 i) (assoc out i 1)
(or (= 0 (out i))
(> (out i) (inc (out (dec i)))))
(assoc out i (inc (out (dec i))))
:else out))
(defn set-j [out i j]
(let [mult (* i j)]
(if (or (= 0 (out mult)) (> (out mult) (inc (out i))))
(assoc out mult (inc (out i)))
out)))
;--------------------------------------------------
; Precompute the values for all possible inputs
;--------------------------------------------------
(defn precompute []
(loop [i 0 out (vec (repeat MAX-NUMBER 0))]
(if (< i MAX-NUMBER)
(recur (inc i) (loop [j 1 new-out (set-i out i)]
(if (and (<= j i) (< (* i j) MAX-NUMBER))
(recur (inc j) (set-j new-out i j))
new-out)))
out)))
;--------------------------------------------------
; Read the number of queries
;--------------------------------------------------
(def num-queries (Integer/parseInt (read-line)))
;--------------------------------------------------
; Precompute the solutions
;--------------------------------------------------
(def values (precompute))
;--------------------------------------------------
; Read and process each query
;--------------------------------------------------
(loop [iter 0]
(if (< iter num-queries)
(do
(println (values (Integer/parseInt (read-line))))
(recur (inc iter)))))
Java代码在我的计算机上的运行时间约为1/10秒,而Clojure代码则需要近2秒钟的时间。由于它是同一台机器,具有相同的JVM,这意味着我在Clojure中做错了事。
人们如何着手翻译此类代码?是什么让它变得如此慢得多的陷阱?
答案 0 :(得分:0)
我将对您的代码进行一些转换(可能与您最初要求的略有不同) 然后解决您更具体的问题。
我知道已经快两年了,但是在遇到您的问题并花了太多时间与之抗争之后 HackerRank及其时间限制,我想我应该发布一个答案。是否在人力资源环境中实现解决方案? 时间限制使我们成为更好的Clojure程序员?我没有得知答案。但我会分享我学到的东西。
我发现相同算法的版本略显苗条。它仍然有两个循环,但是更新仅发生一次
内部循环,并且许多条件都在min
函数中处理。这是我对它的改编:
(defn compute
"Returns a vector of down-to-zero counts for all numbers from 0 to m."
[m]
(loop [i 2 out (vec (range (inc m)))]
(if (<= i m)
(recur (inc i)
(loop [j 1 out out]
(let [ij (* i j)]
(if (and (<= j i) (<= ij m))
(recur (inc j)
(assoc out ij (min (out ij) ;; current value
(inc (out (dec ij))) ;; steps from value just below
(inc (out i))))) ;; steps from a factor
out))))
out)))
请注意,我们仍在使用循环/重复(两次),但仍在使用矢量来保存输出。但是有些区别:
我们将out
初始化为递增整数。这是每个值的最坏情况步数,一次
初始化后,我们不必测试值等于0,就可以跳过索引0和1并在以下位置开始外循环
索引2。(我们还修复了原始文档中的错误,并确保out
包含MAX-NUMBER+1
的值。)
所有三个测试都在封装原始逻辑的min
函数内部进行:一个值将为
仅在步骤数比其下一个数短或其中一个因素短时才更新。
测试现在非常简单,我们不需要将其分解为单独的功能。
此代码(连同您的原始代码)足够快,可以通过HR中的某些测试用例,但不是全部。这里有一些 加快速度的方法:
使用int-array
代替vec
。这意味着我们将使用aset
而不是assoc
和aget
而不是调用out
与索引。这也意味着loop/recur
不再是最好的结构(因为我们不再通过
围绕不可变向量的新版本,但实际上使java.util.Array
发生突变);相反,我们将使用doseq
。
类型提示。仅此一项就产生了巨大的速度差异。测试代码时,请在顶部(set! *warn-on-reflection* true)
包含一个表单,您会看到Clojure在哪里必须做额外的工作来确定它是什么类型
处理。
使用自定义I / O功能读取输入。 HR的样板I / O代码应该让您专注于解决 挑战,而不用担心I / O,但这基本上是垃圾,通常是程序超时的罪魁祸首。
下面是一个包含以上提示的版本,运行速度足以通过所有测试用例。我已经包括了我的习惯
我一直在应对所有人力资源挑战的I / O方法。使用doseq
的一个好处是我们可以添加一个
:let
和绑定形式中的:while
子句,删除了doseq
正文中的某些缩进。也
请注意一些策略性放置的类型提示,它们确实可以加速程序。
(ns down-to-zero-int-array)
(set! *warn-on-reflection* true)
(defn compute
"Returns a vector of down-to-zero counts for all numbers from 0 to m."
^ints [m]
(let [out ^ints (int-array (inc m) (range (inc m)))]
(doseq [i (range 2 (inc m)) j (range 1 (inc i)) :let [ij (* i j)] :while (<= ij m)]
(aset out ij (min (aget out ij)
(inc (aget out (dec ij)))
(inc (aget out i)))))
out))
(let [tokens ^java.io.StreamTokenizer
(doto (java.io.StreamTokenizer. (java.io.BufferedReader. *in*))
(.parseNumbers))]
(defn next-int []
"Read next integer from input. As fast as `read-line` for a single value,
and _much_ faster than `read-line`+`split` for multiple values on same line."
(.nextToken tokens)
(int (.-nval tokens))))
(def MAX 1000000)
(let [q (next-int)
down-to-zero (compute MAX)]
(doseq [n (repeatedly q next-int)]
(println (aget down-to-zero n))))