加速Clojure以避免超时

加速Clojure以避免超时,clojure,Clojure,我做了一些hackerrank挑战,并注意到我似乎无法编写高效的代码,因为我经常超时,即使通过测试的答案是正确的。例如,这是我的代码: (let [divisors (fn [n] (into #{n} (into #{1} (filter (comp zero? (partial rem n)) (range 1 n))))) str->ints (fn [string] (map #(Integer/parseInt %)

我做了一些hackerrank挑战,并注意到我似乎无法编写高效的代码,因为我经常超时,即使通过测试的答案是正确的。例如,这是我的代码:

(let [divisors (fn [n] (into #{n} (into #{1} (filter (comp zero? (partial rem n)) (range 1 n)))))
      str->ints (fn [string]
                  (map #(Integer/parseInt %)
                       (clojure.string/split string #" ")))
      ;lines (line-seq (java.io.BufferedReader. *in*))
      lines ["3"
             "10 4"
             "1 100"
             "288 240"
             ]
      pairs (map str->ints (rest lines))
      first-divs (map divisors (map first pairs))
      second-divs (map divisors (map second pairs))
      intersections (map clojure.set/intersection first-divs second-divs)
      counts (map count intersections)
  ]
  (doseq [v counts]
    (println (str v))))

请注意,
clojure/set
在hackerrank不存在。为了简洁起见,我把它放在这里。

在这种情况下,
map
功能明显被误用: 尽管clojure集合是懒惰的,但对它们的操作仍然不是免费的。因此,当你链接大量的
map
s时,你仍然拥有所有的中间集合(这里有7个)。为了避免这种情况,通常会使用传感器,但在您的情况下,您只是将每一条输入线映射到一条输出线,因此只需在输入集合上进行一次即可:

(let [divisors (fn [n] (into #{n} (into #{1} (filter (comp zero? (partial rem n)) (range 1 n)))))
      str->ints (fn [string]
                  (map #(Integer/parseInt %)
                       (clojure.string/split string #" ")))
                       ;lines (line-seq (java.io.BufferedReader. *in*))
      get-counts (fn [pair] (let [d1 (divisors (first pair))
                                  d2 (divisors (second pair))]
                              (count (clojure.set/intersection d1 d2))))
      lines ["3"
             "10 4"
             "1 100"
             "288 240"
             ]
      counts (map (comp get-counts str->ints) (rest lines))]
  (doseq [v counts]
    (println (str v))))
这里不讨论整个算法的正确性。也许它也可以被优化。但是在clojure的机制中,这个变化应该会显著加快代码的速度

更新

至于算法,您可能希望从限制1..n到1..n(sqrt n)的范围开始,当
x
n
的除数时,将
x
n/x
添加到结果集中,这将为大数带来相当大的利润:

(defn divisors [n]
  (into #{} (mapcat #(when (zero? (rem n %)) [% (/ n %)])
                    (range 1 (inc (Math/floor (Math/sqrt n)))))))
我也会考虑找出所有两个数中最小的除数,然后把另一个数除以。这将消除对较大数的除数的搜索

(defn common-divisors [pair]
  (let [[a b] (sort pair)
        divs (divisors a)]
    (filter #(zero? (rem b %)) divs)))
如果仍然无法通过测试,您可能应该寻找一些不错的公约数算法

(defn common-divisors [pair]
  (let [[a b] (sort pair)
        divs (divisors a)]
    (filter #(zero? (rem b %)) divs)))
更新2


将更新后的算法提交给hackerrank,现在通过得很好

在这种情况下,
map
功能存在明显的误用: 尽管clojure集合是懒惰的,但对它们的操作仍然不是免费的。因此,当你链接大量的
map
s时,你仍然拥有所有的中间集合(这里有7个)。为了避免这种情况,通常会使用传感器,但在您的情况下,您只是将每一条输入线映射到一条输出线,因此只需在输入集合上进行一次即可:

(let [divisors (fn [n] (into #{n} (into #{1} (filter (comp zero? (partial rem n)) (range 1 n)))))
      str->ints (fn [string]
                  (map #(Integer/parseInt %)
                       (clojure.string/split string #" ")))
                       ;lines (line-seq (java.io.BufferedReader. *in*))
      get-counts (fn [pair] (let [d1 (divisors (first pair))
                                  d2 (divisors (second pair))]
                              (count (clojure.set/intersection d1 d2))))
      lines ["3"
             "10 4"
             "1 100"
             "288 240"
             ]
      counts (map (comp get-counts str->ints) (rest lines))]
  (doseq [v counts]
    (println (str v))))
这里不讨论整个算法的正确性。也许它也可以被优化。但是在clojure的机制中,这个变化应该会显著加快代码的速度

更新

至于算法,您可能希望从限制1..n到1..n(sqrt n)的范围开始,当
x
n
的除数时,将
x
n/x
添加到结果集中,这将为大数带来相当大的利润:

(defn divisors [n]
  (into #{} (mapcat #(when (zero? (rem n %)) [% (/ n %)])
                    (range 1 (inc (Math/floor (Math/sqrt n)))))))
我也会考虑找出所有两个数中最小的除数,然后把另一个数除以。这将消除对较大数的除数的搜索

(defn common-divisors [pair]
  (let [[a b] (sort pair)
        divs (divisors a)]
    (filter #(zero? (rem b %)) divs)))
如果仍然无法通过测试,您可能应该寻找一些不错的公约数算法

(defn common-divisors [pair]
  (let [[a b] (sort pair)
        divs (divisors a)]
    (filter #(zero? (rem b %)) divs)))
更新2


将更新后的算法提交给hackerrank,现在它通过得很好

有趣的是,即使有了这些更改,我仍然会在测试用例7、8、9和10上计时。因此,我的代码肯定还有其他一些性能差的问题。这可能是因为低效的除数搜索算法。我会首先尝试通过将搜索数量限制在
(sqrt n)
来优化它。有趣的是,即使有了这些更改,我仍然会在测试用例7、8、9和10上超时。因此,我的代码肯定还有其他一些性能差的问题。这可能是因为低效的除数搜索算法。我将首先尝试通过将搜索数量限制为
(sqrt n)
来优化它。