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)?

Jamal
35.2k13 gold badges134 silver badges238 bronze badges
asked Jun 29, 2017 at 20:47
\$\endgroup\$

1 Answer 1

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
answered Jun 30, 2017 at 19:31
\$\endgroup\$

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.