2
\$\begingroup\$

I was working on Project Euler #109 (Potential spoilers for people who haven't done so yet); I got the correct solution, but I'm not satisfied with how one of my lines of code ended up looking, and was wondering if someone knows a better or more idiomatic way to do this.

singles = [1..20]++[25]
doubles = map (*2) singles
triples = map (*3) [1..20]

alls = singles ++ doubles ++ triples

oneshots = doubles
twoshots = [a+b | a <- doubles , b <- alls]
threeshots = [a+(alls!!b)+(alls!!c) | a <- doubles, b <- [0..((length alls)-1)], c <- [b..((length alls)-1)]]

main = do
    print $ length $ filter (< 100) $ (++oneshots) $ (++twoshots) threeshots

Specifically, the line:

threeshots = [a+(alls!!b)+(alls!!c) | a <- doubles, b <- [0..((length alls)-1)], c <- [b..((length alls)-1)]]

which gives me all of the unordered pairs of elements in alls, gives me pause. If it was a simple range, then I could just do something like:

threeshots = [a+b+c | a <- doubles, b <- [0..100], c <- [b..100]]

and if the list was sorted and each element unique, I could even do:

threeshots = [a+b+c | a <- doubles, b <- alls, c <- alls, c < b]`

However, none of these hold true and my line ends up looking pretty cluttered. What would people recommend as far as reformatting this line (or is it just something I'll have to deal looking at)?

\$\endgroup\$
1
\$\begingroup\$

Your threeshots line

Since using the operator !! is not very Haskelly, let's get rid of that and think of a way to get your b and c using typical list operations. Since you want a list of all pairs, let's create a new function called pairs.

pairs :: [a] -> [(a,a)]
pairs [] = []
pairs (x:xs) = map (\y -> (x, y)) (x:xs) ++ pairs xs

Your new threeshots would thus be:

threeshots = [a + b + c | a <- doubles, (b, c) <- pairs alls]

In a nutshell, pairs takes the first element of the given list, forms tuples with every element of that list and continues with the rest of the list until it's empty.

If you prefer, you could write pairs (x:xs) as map ((,) x) (x:xs) ++ pairs xs. I find that one to be less clear, though.

Other Nitpicks

As a rule of thumb, every top level declaration should have a type annotation.

You've declared your main in a do-block, which is not necessary since you're not using any monadic stuff. The (++oneshots) $ (++twoshots) threeshots part looks unusual, too.

main = print . length . filter (< 100) $ threeshots ++ twoshots ++ oneshots

You can keep the $ in your main if you prefer; in this scenario, it's probably just a matter of taste. Using . is less noise though - to me at least.

Putting it all together

singles, doubles, triples, alls :: [Int]
singles = [1..20] ++ [25]
doubles = map (*2) singles 
triples = map (*3) [1..20]
alls    = singles ++ doubles ++ triples

pairs :: [a] -> [(a, a)]
pairs []     = []
pairs (x:xs) = map (\y -> (x, y)) (x:xs) ++ pairs xs

oneshots, twoshots, threeshots :: [Int]
oneshots   = doubles
twoshots   = [a + b | a <- doubles, b <- alls]
threeshots = [a + b + c | a <- doubles, (b, c) <- pairs alls]

main :: IO ()
main = print . length . filter (< 100) $ threeshots ++ twoshots ++ oneshots
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.