简体   繁体   English

通过递归检查尾部来迭代列表的性能

[英]Performance of iterating a list by recursively examining the tail

I decided to try to learn Haskell by doing some of the CodinGame challenges (so this question is super beginner-level stuff I'm sure). 我决定尝试通过做一些CodinGame挑战来学习Haskell(所以这个问题是超级初学者级的东西,我敢肯定)。 One of them requires searching through a list of integers for the smallest difference between any two values. 其中一个需要在整数列表中搜索任意两个值之间的最小差异。 I've previously solved it in Clojure by doing this: 我以前通过这样做在Clojure中解决了它:

(ns Solution
  (:gen-class))

(defn smallest-difference [values]
  (let [v (sort values)]
    (loop [[h & t] v curr-min 999999]
      (if (nil? t) curr-min
        (let [dif (- (first t) h)]
          (recur t (if (> curr-min dif) dif curr-min)))))))

(defn -main [& args]
  (let [horse-strengths (repeatedly (read) #(read))]
      (let [answer (smallest-difference horse-strengths)]
        (println answer)))) 

I tried to implement the same solution in Haskell, with the following: 我尝试在Haskell中实现相同的解决方案,具体如下:

readHorses :: Int -> [Int] -> IO [Int]
readHorses n h
    | n < 1 = return h
    | otherwise = do
        l <- getLine
        let hn = read l :: Int
        readHorses (n - 1) (hn:h)

findMinDiff :: [Int] -> Int -> Int
findMinDiff h m
    | (length h) < 2    = m
    | (h!!1 - h!!0) < m = findMinDiff (tail h) (h!!1 - h!!0)
    | otherwise         = findMinDiff (tail h) m



main :: IO ()
main = do
    hSetBuffering stdout NoBuffering -- DO NOT REMOVE
    input_line <- getLine
    let n = read input_line :: Int
    hPrint stderr n
    horses <- readHorses n []
    hPrint stderr "Read all horses"
    print (findMinDiff (sort horses) 999999999)
    return ()

This times-out for large inputs (99999 values) where the Clojure solution does not. 对于Clojure解决方案没有的大输入(99999值),这次超时。 They look pretty similar to me however. 然而,它们看起来与我很相似。

It at least superficially seems that reading the values and constructing the list isn't the problem, as "Read all horses" is printed before the timeout. 至少从表面上看,读取值并构建列表不是问题,因为在超时之前会打印“Read all horses”。

How can I make the Haskell version more performant? 如何使Haskell版本更高效?

You're calculating the length of the list in every recursion into findMinDiff . 您在每次递归中计算列表的lengthfindMinDiff Since length takes O(n) time findMinDiff takes O(n^2) time instead of O(n) . 由于length需要O(n)时间,因此findMinDiff需要O(n^2)时间而不是O(n)

You can write the same thing with pattern matching instead of length , !! 您可以使用模式匹配而不是length编写相同的东西, !! , and tail tail

findMinDiff :: [Int] -> Int -> Int
findMinDiff (h0 : hs@(h1 : _)) m = 
    if h1 - h0 < m
    then findMinDiff hs (h1 - h0)
    else findMinDiff hs m
findMinDiff _ m = m

By the way, a completely alternative implementation could be written as follows. 顺便说一下,完全替代的实现可以写成如下。 (Pseudocode follows) (伪代码如下)

Take the list 拿这份清单

h = [h0, h1, h2 ...

Remove one element 删除一个元素

drop 1 h = [h1, h2, h3 ...

Compute pointwise differences 计算逐点差异

zipWith (-) (drop 1 h) h = [h1-h0, h2-h1, h3-h2, ...

Then take the minimum. 然后采取最低限度。 Full code: 完整代码:

minDiff :: [Int] -> Int
minDiff h = minimum (zipWith (-) (drop 1 h) h)

Note that this will crash on the empty list. 请注意,这将在空列表中崩溃。 On the other hand, there's no need of the 9999999 hack. 另一方面,没有必要使用9999999 hack。 Thanks to laziness, it runs in constant space, too. 由于懒惰,它也在恒定的空间中运行。

For a better error handling: 为了更好的错误处理:

minDiff :: [Int] -> Int
minDiff [] = error "minDiff: empty list"
minDiff h = minimum (zipWith (-) (drop 1 h) h)

or even, more pedantically (but respecting totality): 甚至,更迂腐(但尊重整体):

minDiff :: [Int] -> Maybe Int
minDiff [] = Nothing
minDiff h  = Just (minimum (zipWith (-) (drop 1 h) h))

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM