I have this piece of code which given a list returns another list which combines every element with every other element (without repetitions) using a given function as a combiner. I came up with three different version (2 of them listed below, fastest to slowest), and now I'm asking for help to optimize it/them even further. Currently, my perms3 does not utilize multithreading at all, and since I'm quite new to Haskell I can't seem to figure out why, but I'm thinking that in its current state my algorithm isn't parallelizable, even though it can be.
I've also concluded that the length of the output list will always be \$ \binom{x}{2} + x = x(x-1)/2 + x\$ where \$x\$ is the length of the input list, but I dont know how to use that in my algorithm either to minimize the allocations.
The reason the why I'm asking is because I want to know more about the inner workings of Haskell, how lazy-evaluation affects performance and what is considered good/bad Haskell code.
My two permutation functions:
perms3 c l = perms3' c l l
perms3' _ [] _ = []
perms3' c (a:b) [x] = c a x:perms3' c b b -- the outer loop
perms3' c l@(a:_) (h:t) = c a h:perms3' c l t -- the inner loop
perms2 _ [] = [] --this can probably be 2 folds, the inner fold might be parallelizable?
perms2 c l@(h:t)= foldr (\x acc -> c h x:acc) [] l ++ perms2 c t
Input: (,) ['a'..'c']
Output: [('a','a'),('a','b'),('a','c'),('b','b'),('b','c'),('c','c')]
Input (+) [1..5]
Output: [2,3,4,5,6,4,5,6,7,6,7,8,8,9,10]
Unsurprisingly, perms3 is a fair bit faster than perms2.
Testing perms3:
import Control.Monad
import System.Environment
main :: IO ()
main = liftM (length . perms3 (,) . enumFromTo 1 . read . head) getArgs >>= print
perms3 :: (a -> a -> t) -> [a] -> [t]
perms3 c l = perms3' c l l
perms3' _ [] _ = []
perms3' c (a:b) [x] = c a x:perms3' c b b
perms3' c l@(a:_) (h:t) = c a h:perms3' c l t
Running it:
$ ghc -O3 main && time ./main 10000 50005000 real 0m0.615s $ ghc -O3 main && time ./main 30000 450015000 real 0m5.347s
Question(s):
- Is this a good way to write Haskell code?
- Can I squeeze any more performance out of my function?
- Should I prefer the imperative do-notation instead of arrows in my main function?