Reputation: 2852
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). 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:
(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:
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. 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.
How can I make the Haskell version more performant?
Upvotes: 2
Views: 135
Reputation: 116139
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. 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))
Upvotes: 4
Reputation: 24156
You're calculating the length
of the list in every recursion into findMinDiff
. Since length
takes O(n)
time findMinDiff
takes O(n^2)
time instead of O(n)
.
You can write the same thing with pattern matching instead of length
, !!
, and 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
Upvotes: 5