2025/Day02
Parsing
1-4,9-13
The input is represented as list of minimum and maximum bounds separated by a -, each pair separated by a ,.
So I decided to represent them as a list of tuples.
To pare that list I transformed the input string and used the reading capabilities.
- First I separate each pair into its own string by splitting the input on each
, - Then I replace the
-by,which are used in the tuple representation - I add the parenthesis to complete the tuple representation
type Input = [(Int, Int)]
parseInput :: String -> Input
parseInput = map (read . ("(" ++) . (++ ")") . replace "-" ",") . splitOn ","
Part 1
For part one, we need to figure out which number has been created by copying the same pattern twice.
eg: 123123 can be created by copying the pattern 123 twice
So my solution I developed the following algorithm:
for the string
abcabd
123456 – the indices
-
I will first check that the number of digit is even:
Here
6is even -
Then I will take the first half of the string:
Here
abcis my pattern -
Next I will split the string on that pattern:
My word starts with the pattern so the first result will be “”.
Then the word starts like the pattern but does not finish, the second result will be “abd”
So the result will be
["", "abd"] -
To finish I will verify that all the strings are actually empty:
In this example, the first element is empty but the second one is not, so the number can’t be built by repeating the same pattern twice.
Here is the actual code checking this information
isInvalidForN :: Int -> String -> Bool
isInvalidForN n s
| l `mod` n /= 0 = False -- the length of the word is not a multiple of the number of parts, it cannot be built
| otherwise = all null $ splitOn patt s -- Are all the splits of the pattern empty
where
l = length s
delta = div l n -- Get the length of the pattern
patt = take delta s -- Get the pattern by taking the first chars in the string
sumInvalid :: (String -> Bool) -> [(Int, Int)] -> Int
sumInvalid isInvalid = sum . filter (isInvalid . show) . concat . map (uncurry enumFromTo) -- sum the invalid numbers within the given ranges
part1 :: Input -> Output
part1 = sumInvalid (isInvalidForN 2) -- get the results by checking with the pattern repeated twice
Part 2
For part two, we need to check if there is a pattern repeated at least twice to form the number.
For this we can have the following algorithm:
-
Create a list of every number until the size of the string starting with 2 (all the number of times we can see the pattern repeating)
-
For every number, filter the ones for which the length of the string is a multiple
-
Using the previous algorithm, check if they are valid for each of them (stop at the first repeating pattern)
Now the code for this algorithm :
part2 :: Input -> Output
part2 = sumInvalid isInvalidForAll -- Sum all the invalid defined by the following function
where
isInvalidForAll s = not . null . filter (`isInvalidForN` s) $ [2 .. length s] -- Check if for any number from 2 to the length of the string you can find a repeating pattern
Complete code
type Input = [(Int, Int)]
parseInput :: String -> Input
parseInput = map (read . ("(" ++) . (++ ")") . replace "-" ",") . splitOn ","
isInvalidForN :: Int -> String -> Bool
isInvalidForN n s
| l `mod` n /= 0 = False
| otherwise = all null . splitOn (take delta s) $ drop delta s -- Removing the first pattern to speed up the process
where
l = length s
delta = div l n
sumInvalid :: (String -> Bool) -> [(Int, Int)] -> Int
sumInvalid isInvalid = sum . filter (isInvalid . show) . concat . map (uncurry enumFromTo)
part1 :: Input -> Output
part1 = sumInvalid isDouble
where
-- Here another version specific for 2 verifying if both sides are equal
-- Slightly faster
-- isDouble s = uncurry (==) $ splitAt (length s `div` 2) s
-- Using existing code
isDouble = isInvalidForN 2
part2 :: Input -> Output
part2 = sumInvalid isInvalidForAll
where
isInvalidForAll s = not . null . filter (`isInvalidForN` s) $ [2 .. length s]
An other version cousd have changed the code :
splitOn (take delta s) $ drop delta s
Into
uncurry splitOn $ splitAt delta s
to make it cleaner, but this solution is slower so I chose the one slightly less readable for optimization and to avoid confusion by thinking it does a half split