【问题标题】:Clojure translate from JavaClojure 从 Java 翻译
【发布时间】:2018-09-02 19:32:42
【问题描述】:

我开始学习 Clojure,并决定在 HackerRank 上做一些项目是一个很好的方法。我发现我的 Clojure 解决方案非常慢。我假设那是因为我仍然在思考,或者只是对 Clojure 的运作方式知之甚少。我写解决方案的最新问题是归零 II。这是我的 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 中做错了。

人们如何尝试翻译这种类型的代码?是什么导致它这么慢?

【问题讨论】:

  • 你是如何运行你的代码的?您是在使用lein,将.clj 文件传递​​给clojure 还是其他方式?
  • 我只是想补充一点:我没有看问题陈述,但您正在使用loop/recur 进行迭代。 Clojure 更喜欢 forreducemap。如果您寻找 clojure 性能提示,还有更多内容,例如类型提示。
  • (vec (repeat 1e6 0)) 非常昂贵。它正在创建一百万个充满 0 的元素向量。仅在我的低频手机上就需要整整一秒钟。另一方面,(int-array 1e6 0) 需要 14 毫秒。如果您确实需要存储这么多数据,那么数组可能是更好的选择。不过,我无法在您的代码中看到任何效率过低的地方。不过,我会说,如果您将其发布在 Code Review 上,如果您愿意,我会在明天对其进行审查。好像有几个地方可以提建议。
  • 从我读过的一些内容中,我得到印象循环/重复执行比减少更好。它基本上是一个嵌套循环,它设置 A[i],然后 for j
  • 似乎 map 应该是要走的路,但我看不出如何让 map 设置多个值或如何链接它以使结果向量具有正确的条目。

标签: clojure


【解决方案1】:

我将对您的代码进行一些转换(可能稍微超出您最初的要求) 然后解决您更具体的问题。

我知道已经快两年了,但是在遇到您的问题并花了太多时间与 HackerRank 及其时间限制,我想我会发布一个答案。是否在 HR 的环境中实现解决方案? 时间限制让我们成为更好的 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)))

请注意,我们仍在使用循环/递归(两次),我们仍在使用向量来保存输出。但有些区别:

  1. 我们将out 初始化为递增整数。这是每个值的最坏情况步数,并且一次 初始化后,我们不必测试值是否等于 0,我们可以跳过索引 0 和 1 并开始外循环 索引 2。(我们还修复了您原始文件中的一个错误,并确保 out 包含 MAX-NUMBER+1 值。)

  2. 所有三个测试都发生在封装原始逻辑的min 函数中:一个值将是 仅当距其下方的数字或其中一个因素的步数较短时才更新。

  3. 现在测试已经足够简单了,我们不需要将它们分解成单独的函数。

这段代码(连同您的原始代码)足够快,可以通过 HR 中的一些测试用例,但不是全部。这里有一些 加快速度的方法:

  1. 使用int-array 代替vec。这意味着我们将使用aset 而不是assocaget 而不是调用out 带索引。这也意味着loop/recur 不再是最好的结构(因为我们不再通过 围绕不可变向量的新版本,但实际上改变了java.util.Array);相反,我们将使用doseq

  2. 输入提示。仅这一点就产生了巨大的速度差异。在测试您的代码时,在顶部添加一个表单 (set! *warn-on-reflection* true),您将看到 Clojure 必须在哪些地方做额外的工作来确定它是什么类型 处理。

  3. 使用自定义 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))))

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-05-18
    • 1970-01-01
    相关资源
    最近更新 更多