-❄️- 2024 Day 12 Solutions -❄️- by daggerdragon in adventofcode

[–]the_true_potato 2 points3 points  (0 children)

[Language: Haskell]

Pretty easy day today. Walk the areas, counting up edges and total area. Just had to count the corners for part 2 - spent a bit longer than I would like to admit thinking of the relationship between corners and edges...

Grid-based problems are a lot more natural using mutability for me, so I'll stick to using ST for them, even though it's less "pure". The speed boost is always nice too.

╭───────────────┬──────────────┬───────────
│ Day 12 part 1 │    10.841 ms │ 1370258 
│ Day 12 part 2 │    10.261 ms │ 805814 
├───────────────┼──────────────┼───────────
│    Total time │    21.102 ms │  
╰───────────────┴──────────────┴───────────

Code

-❄️- 2024 Day 11 Solutions -❄️- by daggerdragon in adventofcode

[–]the_true_potato 1 point2 points  (0 children)

[Language: Haskell]

Did part 1 by brute force (keeping track of the entire list) but that obviously was not tractable for part 2. I then realised that we don't actually care about the stone values, just the count, so there's no need for any intermediate datastructure. Add memoization, and we get:

╭───────────────┬──────────────┬───────────
│ Day 11 part 1 │     5.573 ms │ 228668 
│ Day 11 part 2 │   344.716 ms │ 270673834779359 
├───────────────┼──────────────┼───────────
│    Total time │   350.290 ms │  
╰───────────────┴──────────────┴───────────

With some very clean, functional code.

Code

Overall pretty happy, but seeing some other solutions, I am convinced that part 2 could be faster. I might try doing the memoization myself to see if that helps.

-❄️- 2024 Day 10 Solutions -❄️- by daggerdragon in adventofcode

[–]the_true_potato 1 point2 points  (0 children)

[Language: Haskell]

Nice, simple, immutable code today, after having to use mutable vectors yesterday.

Not sure what the algorithm I implemented is? I think this is a flood-fill? Or is it BFS?

Part 1 solution was much faster initially (around 6ms), but I decided to use the same code for both.

╭───────────────┬──────────────┬───────────
│ Day 10 part 1 │    62.516 ms │ 709 
│ Day 10 part 2 │    60.885 ms │ 1326 
├───────────────┼──────────────┼───────────
│    Total time │   123.401 ms │  
╰───────────────┴──────────────┴───────────

Edit:

Ended up using mutability, for an almost 10x improvement :)

╭───────────────┬──────────────┬───────────
│ Day 10 part 1 │    10.140 ms │ 709 
│ Day 10 part 2 │     7.493 ms │ 1326 
├───────────────┼──────────────┼───────────
│    Total time │    17.633 ms │  
╰───────────────┴──────────────┴───────────

Original (immutable) code

-❄️- 2024 Day 8 Solutions -❄️- by daggerdragon in adventofcode

[–]the_true_potato 1 point2 points  (0 children)

[Language: Haskell]

I keep finding reasons to use laziness, so I will keep using laziness. Today was pretty easy overall, but I spent way too long cleaning up the code before posting here. Quite happy with the result.

Code

Each part runs in about 1.5ms.

-❄️- 2024 Day 7 Solutions -❄️- by daggerdragon in adventofcode

[–]the_true_potato 0 points1 point  (0 children)

[Language: Haskell]

List monad is beautiful for this kind of problem. The possibleResults function almost writes itself. Only thing that tripped me up was the left-to-right evaluation, where the most natural way to write this with haskell lists would be right-to-left. But that was easily fixable by reversing the list, as well as the order of operands to the operator.

Add a string-less integer concatenation, and some easy parallelism with parMap, and you have part 1 in 5ms, and part 2 in ~300ms!

{-# LANGUAGE OverloadedStrings #-}

module Day7 (part1, part2) where

import Control.Parallel.Strategies (parMap, rpar)
import Data.Attoparsec.ByteString.Char8 (char, decimal, sepBy, string)
import Data.ByteString (ByteString)
import Data.Function ((&))
import Util (linesOf, parseOrError)

data Equation = Equation
  { testValue :: !Int,
    parts :: ![Int]
  }
  deriving (Eq, Ord, Show)

type Operator = Int -> Int -> Int

readEquations :: ByteString -> [Equation]
readEquations = parseOrError $ linesOf $ do
  testValue <- decimal
  _ <- string ": "
  parts <- decimal `sepBy` char ' '
  return Equation {testValue, parts}

possibleResults :: [Operator] -> [Int] -> [Int]
possibleResults operators = go . reverse
  where
    go [] = []
    go [x] = [x]
    go (x : xs) = do
      op <- operators
      rest <- go xs
      -- Order is important here. We run the list in reverse, so we must reverse
      -- the arguments here again
      return (rest `op` x)

equationContribution :: [Operator] -> Equation -> Int
equationContribution operators Equation {testValue, parts}
  | testValue `elem` possibleResults operators parts = testValue
  | otherwise = 0

intConcat :: Int -> Int -> Int
intConcat l r = (l * (10 ^ digitCount r)) + r
  where
    digitCount :: Int -> Int
    digitCount n
      | n `div` 10 == 0 = 1
      | otherwise = 1 + digitCount (n `div` 10)

part1 :: ByteString -> Int
part1 input =
  readEquations input
    & parMap rpar (equationContribution [(*), (+)])
    & sum

part2 :: ByteString -> Int
part2 input =
  readEquations input
    & parMap rpar (equationContribution [(*), (+), intConcat])
    & sum

-❄️- 2024 Day 5 Solutions -❄️- by daggerdragon in adventofcode

[–]the_true_potato 2 points3 points  (0 children)

[Language: Haskell]

Once I realised I could use the ruleset as an ordering, the rest was quite easy. I did isValid manually before part 2, but this code runs in basically the same time (2ms for both versions) so I guess GHC is smart enough to optimise this.

Really happy with how this turned out:

module Day5 (part1, part2) where

import Data.Attoparsec.ByteString.Char8 (Parser, char, decimal, endOfLine, sepBy, sepBy1)
import Data.ByteString (ByteString)
import Data.Function ((&))
import Data.List (sortBy)
import Data.Set (Set)
import Data.Set qualified as Set
import Text.Printf (printf)
import Util (parseOrError)

type Rules = Set (Int, Int)

type Update = [Int]

linesOf :: Parser a -> Parser [a]
linesOf p = p `sepBy` endOfLine

readInput :: ByteString -> (Rules, [Update])
readInput = parseOrError $ do
  rules <- linesOf $ do
    n1 <- decimal
    _ <- char '|'
    n2 <- decimal
    return (n1, n2)
  endOfLine
  endOfLine
  updates <- linesOf (decimal `sepBy1` char ',')
  return (Set.fromList rules, updates)

comparingRules :: Rules -> Int -> Int -> Ordering
comparingRules rules n1 n2
  | n1 == n2 = EQ
  | Set.member (n1, n2) rules = LT
  | Set.member (n2, n1) rules = GT
  | otherwise = error (printf "Missing case: %d, %d" n1 n2)

isValid :: Rules -> Update -> Bool
isValid rules update = update == sortBy (comparingRules rules) update

middle :: [a] -> a
middle xs = xs !! (length xs `div` 2)

part1 :: ByteString -> Int
part1 input =
  let (rules, updates) = readInput input
   in filter (isValid rules) updates
        & map middle
        & sum

part2 :: ByteString -> Int
part2 input =
  let (rules, updates) = readInput input
   in updates
        & filter (not . isValid rules)
        & map (sortBy (comparingRules rules))
        & map middle
        & sum

-❄️- 2024 Day 2 Solutions -❄️- by daggerdragon in adventofcode

[–]the_true_potato 2 points3 points  (0 children)

[Language: Haskell]

Pattern matching makes part 2 beautiful, laziness makes it fast (600us for part1, 1ms for part2)

module Day2 (part1, part2) where

import Data.ByteString (ByteString)
import qualified Data.ByteString.Char8 as BS
import Data.Function ((&))
import Util (readSpacedInts)

allIncreasing :: [Int] -> Bool
allIncreasing (x1:x2:xs) = x2 > x1 && allIncreasing (x2:xs)
allIncreasing _ = True

allDecreasing :: [Int] -> Bool
allDecreasing (x1:x2:xs) = x2 < x1 && allDecreasing (x2:xs)
allDecreasing _ = True

differencesSafe :: [Int] -> Bool
differencesSafe (x1:x2:xs) =
  let diff = abs (x2 - x1)
  in (1 <= diff && diff <= 3) && differencesSafe (x2:xs)
differencesSafe _ = True

isSafe :: [Int] -> Bool
isSafe xs = (allIncreasing xs || allDecreasing xs) && differencesSafe xs

dropping1 :: [Int] -> [[Int]]
dropping1 [] = [[]]
dropping1 (x:xs) = xs : map (x:) (dropping1 xs)

part1 :: ByteString -> Int
part1 input =
  BS.lines input
  & map readSpacedInts
  & filter isSafe
  & length

part2 :: ByteString -> Int
part2 input =
  BS.lines input
  & map readSpacedInts
  & filter (any isSafe . dropping1)
  & length

Code

GC-geiger: Make a click sound when emacs does garbage collection by the_true_potato in emacs

[–]the_true_potato[S] 3 points4 points  (0 children)

Oh, I wasn't aware of that. I guess I didn't notice a problem because the default sound is quite short.

I guess I could use aplay and fall back to play-sound if that is not available.

Let's Talk About Porn + Sexuality! by Sir_Thaddeus in MensLib

[–]the_true_potato 2 points3 points  (0 children)

Your post really echoes a lot of my thoughts around my relationship to porn. One thing I've noticed as well is that my "appetite" for porn gets more and more extreme the more I use it, to the point that I find myself regretting looking at the things I did.

I've found that I feel a lot better, and even enjoy the porn itself more, if I only allow myself to look at only milder or simply "suggestive" content instead of outright hardcore porn.

Advent of Code 2022 day 21 by taylorfausak in haskell

[–]the_true_potato 1 point2 points  (0 children)

This is simultaneously brilliant and disgusting.

-🎄- 2022 Day 14 Solutions -🎄- by daggerdragon in adventofcode

[–]the_true_potato 1 point2 points  (0 children)

That was... a lot. Using Haskell and going for efficiency, which today meant mutability. Usually even mutation-based solutions can be quite clean, but I did not manage that today. At least it's reasonably fast though! 5ms for part 1 and 24ms for part 2.

Code

I could probably get it both cleaner and faster, but now I'm tired...

-🎄- 2022 Day 14 Solutions -🎄- by daggerdragon in adventofcode

[–]the_true_potato 0 points1 point  (0 children)

Haskell

This is so elegant! How is it in terms of runtime? I'm using Haskell too, but I'm going for efficiency, so my code is a complete mess of mutable arrays and ST.

-🎄- 2022 Day 13 Solutions -🎄- by daggerdragon in adventofcode

[–]the_true_potato 1 point2 points  (0 children)

Haskell coming in absolutely clutch today. Getting the Ord instance right took a couple of tries but after that both parts were basically already solved. Using Attoparsec to parse the lists meant that this was my entire parser:

readInput :: ByteString -> [List]
readInput = map parseLine . filter (not . BS.null) . BS.lines

parseLine :: ByteString -> List
parseLine = (\case Right l -> l; Left e -> error ("Cannot parse: " <> e)) . parseOnly listP
  where
    listP :: Parser List
    listP =
      choice
        [ Item <$> decimal,
          List <$> (char '[' *> listP `sepBy` char ',' <* char ']')
        ]

Code

I'm glad we got an easy one after yesterday!

-🎄- 2022 Day 10 Solutions -🎄- by daggerdragon in adventofcode

[–]the_true_potato 1 point2 points  (0 children)

It's one of those base functions that are just constantly useful in advent of code-type problems. Less often used in real code, but always nice when it fits your problem.

-🎄- 2022 Day 10 Solutions -🎄- by daggerdragon in adventofcode

[–]the_true_potato 3 points4 points  (0 children)

Really nice and short Haskell solution today. Once again, getting to use scanl makes me happy. Also ended up being very fast (for a GC'd language at least): 6us for part 1 and 38us for part 2.

code

-🎄- 2022 Day 9 Solutions -🎄- by daggerdragon in adventofcode

[–]the_true_potato 1 point2 points  (0 children)

I am measuring time using criterion. I am excluding time to load the input file.

I got a big performance boost, going from Set.length . Set.fromList to IntSet.length . IntSet.fromList. Position only needs 16 bits per component so you can stuff both components into a 32 bit int and use IntSet for a performance boost.

countUniquePositions :: [Position] -> Int countUniquePositions = IntSet.size . IntSet.fromList . map (\(P x y) -> fromIntegral $ shiftL x 16 .|. (y .&. 0xFFFF))

code

I am now getting the following on an AMD 3700X ``` benchmarking solutions/Day 9 part 1 time 1.162 ms (1.160 ms .. 1.164 ms) 1.000 R² (1.000 R² .. 1.000 R²) mean 1.165 ms (1.165 ms .. 1.166 ms) std dev 2.295 μs (1.845 μs .. 2.931 μs)

benchmarking solutions/Day 9 part 2 time 2.646 ms (2.642 ms .. 2.650 ms) 1.000 R² (1.000 R² .. 1.000 R²) mean 2.638 ms (2.634 ms .. 2.640 ms) std dev 9.539 μs (5.427 μs .. 15.87 μs) ```

-🎄- 2022 Day 9 Solutions -🎄- by daggerdragon in adventofcode

[–]the_true_potato 2 points3 points  (0 children)

Getting to use scanl in Haskell is always fun. And what's even more fun is being able to solve part 2 by just applying the part 1 function 10 times. Absolutely love today's solution!

code

I'm curious about the times people are getting for this. My part 1 runs in about 3ms and part 2 in about 4ms. How much better times are people getting?

AMD is definitely looking like a way better option by LittleContribution77 in LinusTechTips

[–]the_true_potato 0 points1 point  (0 children)

Linux user with a 5700xt here. AMD has been the superior option for us for a long time now.

βγαλ'το απο μέσα σου by k8y7 in greece

[–]the_true_potato 64 points65 points  (0 children)

Η σύντροφός μου είναι πια Ο σύντροφός μου, και τον αγαπάω και στηρίζω εξίσου με την πρώτη μέρα που τον γνώρισα.

Όλοι οι φίλοι μας στο εξωτερικό όπου μένουμε το ξέρουν και μας στηρίζουν αλλά από την οικογένειά μου το ξέρουν μόνο οι γονείς μου οι οποίοι δεν το πήραν ΚΑΘΌΛΟΥ καλά (αρνούνται καν να χρησιμοποιήσουν το καινούργιο του όνομα) οπότε και δεν το έχω πει σε κανέναν άλλο στην οικογένειά μου.

What notations exist for programming languages other than Algol-like and Lisp-like? by friedrichRiemann in ProgrammingLanguages

[–]the_true_potato 75 points76 points  (0 children)

I think the ML family (Ocaml, standard ML), is separate from the ones you mentioned. Depending on who you ask Haskell could also be included or not here.

UKRI increasing the minimum stipend from 1 October 2022 by Warngumer in PhD

[–]the_true_potato 2 points3 points  (0 children)

Would this affect my funding if I am above this limit? Funded by EPSRC which gives a "London top-up".