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