Advent of Code 2020 in Haskell - Day 2



Jump to:

D1 | D2 | D3 | D4 | D5 | D6 | D7 | D8 | D9 | D10 | D11 | D12 | D13 | D14 | D15 | D16 | D17 | D18 | D19 | D20 | D21 | D22 | D23 | D24 | D25


Part 1

Day 2 asks us to perform some basic string validation. Again, I didn't feel like parsing the input, so I ran the input through a series of substitutions so that I could deal directly with the input as a [(Int, Int, Char, String)].

    
import D2input
-- input :: [(Int, Int, Char, String)]

isValid :: (Int, Int, Char, String) -> Bool
isValid (min, max, c, pw) = 
    let occurrences = length $ filter (==c) pw
    in (occurrences >= min && occurrences <= max)

solve :: Int
solve = length $ filter isValid input

main :: IO ()
main = print solve
    

Once we have the input, though, this problem is fairly simple: to determine whether a word is valid, count the occurrences of the specified character and verify that it falls within the range. Then, count the number of valid words.

Perhaps worth calling out is the use of length and filter to implement the "count if" algorithm, which is used both to validate passwords and to count the number of valid passwords. To those unfamiliar with Haskell, this looks like an inefficient implementation, as a naive read would lead one to believe that one must do one pass to filter the list, then one pass to determine the length. However, this is likely to be implemented in one pass due to the way Haskell evaluates such expressions.

Part 2

In part 2, we need to deal with a slightly more complicated validation criterion.

    
import D2input
-- input :: [(Int, Int, Char, String)]

xor :: Bool -> Bool -> Bool
xor x y = (x && not y) || (not x && y)

isValid :: (Int, Int, Char, String) -> Bool
isValid (i1, i2, c, pw) = 
    let firstMatch = (pw !! (i1 - 1)) == c
        secondMatch = (pw !! (i2 - 1)) == c
    in xor firstMatch secondMatch

solve :: Int
solve = length $ filter isValid input

main :: IO ()
main = print solve
    

(Do I really need to write my own xor function? Am I missing something?)

We take a similar approach, but update our isValid function. The (!!) operator indexes into the String (which is a list of Char in Haskell), and our function simply ensures that exactly one of the characters match (making sure that we subtract one to account for the 1-indexing vs 0-indexing).

In this case, I think we are doing two passes through the password: once to check the first position, and once to check the second position. We could probably rejigger things to collapse this down to one pass as well, but it didn't seem worth it.


Jump to:

D1 | D2 | D3 | D4 | D5 | D6 | D7 | D8 | D9 | D10 | D11 | D12 | D13 | D14 | D15 | D16 | D17 | D18 | D19 | D20 | D21 | D22 | D23 | D24 | D25