[โ€“] 4 points 1 year ago* (1 child)

It's gradually coming back to me. The Haskell Complex type doesn't work particularly nicely as an integer, plus the definition of division is more like "scale", so I just went with my own type.

Then I forgot which of div and quot I should use, and kept getting nearly the right answer :/

import Data.Ix  

data CNum = CNum !Integer !Integer  

instance Show CNum where  
  show (CNum x y) = "[" ++ show x ++ "," ++ show y ++ "]"  

cadd, cmul, cdiv :: CNum -> CNum -> CNum  
(CNum x1 y1) `cadd` (CNum x2 y2) = CNum (x1 + x2) (y1 + y2)  
(CNum x1 y1) `cmul` (CNum x2 y2) = CNum (x1 * x2 - y1 * y2) (x1 * y2 + y1 * x2)  
(CNum x1 y1) `cdiv` (CNum x2 y2) = CNum (x1 `quot` x2) (y1 `quot` y2)  

part1 a = iterate op (CNum 0 0) !! 3  
  where  
    op x = ((x `cmul` x) `cdiv` CNum 10 10) `cadd` a  

countEngraved = length . filter engrave  
  where  
    engrave p =  
      let rs = take 100 $ tail $ iterate (op p) (CNum 0 0)  
       in all (\(CNum x y) -> abs x <= 1000000 && abs y <= 1000000) rs  
    op p r = ((r `cmul` r) `cdiv` CNum 100000 100000) `cadd` p  

part2 a =  
  countEngraved  
    . map (\(y, x) -> a `cadd` CNum (x * 10) (y * 10))  
    $ range ((0, 0), (100, 100))  

part3 a =  
  countEngraved  
    . map (\(y, x) -> a `cadd` CNum x y)  
    $ range ((0, 0), (1000, 1000))  

main = do  
  print $ part1 $ CNum 164 56  
  print $ part2 $ CNum (-21723) 67997  
  print $ part3 $ CNum (-21723) 67997  
  • source
  • [โ€“] 5 points 1 year ago* (last edited 1 year ago) (2 children)

    Ooh, challenges! Here we go!

    I haven't really written any Haskell since last year's AoC, and boy am I rusty.

    import Control.Monad  
    import Data.List  
    import Data.List.Split  
    import Data.Vector qualified as V  
    
    readInput s =  
      let [names, _, moves] = splitOn "," <$> lines s  
       in (names, map readMove moves)  
      where  
        readMove (d : s) =  
          let n = read s :: Int  
           in case d of  
                'L' -> -n  
                'R' -> n  
    
    addWith f = (f .) . (+)  
    
    part1 names moves =  
      names !! foldl' (addWith $ clamp (length names)) 0 moves  
      where  
        clamp n x  
          | x < 0 = 0  
          | x >= n = n - 1  
          | otherwise = x  
    
    part2 names moves =  names !! (sum moves `mod` length names)  
    
    part3 names moves =  
      V.head  
        . foldl' exchange (V.fromList names)  
        $ map (`mod` length names) moves  
      where  
        exchange v k = v V.// [(0, v V.! k), (k, V.head v)]  
    
    main =  
      forM_  
        [ ("everybody_codes_e2025_q01_p1.txt", part1),  
          ("everybody_codes_e2025_q01_p2.txt", part2),  
          ("everybody_codes_e2025_q01_p3.txt", part3)  
        ]  
        $ \(input, solve) ->  
          readFile input >>= putStrLn . uncurry solve . readInput  
    
  • source
  • [โ€“] 2 points 1 year ago

    I coded this along with my girlfriend who's learning python, but not motivated to share her solution. The program reads from stdin, because I usually invoke it like so: runhaskell Main.hs < input or runhaskell Main.hs < example. I think this is quite handy because I don't have to change the source code to check the example input again.

    I struggled with Part 3, where I suddenly forgot I could've simply used mod, which I ended up doing anyway. I immediately recognized that Part 3 needs Mutable Arrays if I care to avoid Index hell, which is not what I wanted to with Haskell but oh well.

    {-# OPTIONS_GHC -Wall #-}
    {-# LANGUAGE PatternSynonyms #-}
    module Main (main) where
    
    import qualified Data.Text as Text
    import qualified Data.Text.IO as TextIO
    import Control.Monad ((<$!>), forM_)
    import Data.Text (Text, pattern (:<))
    import qualified Data.List as List
    import qualified Data.Array.MArray as MutableArray
    import Control.Monad.ST (runST, ST)
    import Data.Array.ST (STArray)
    
    commaSepLine :: IO [Text.Text]
    commaSepLine = Text.split (== ',') <$!> TextIO.getLine
    
    readInstruction :: Text -> Int
    readInstruction ('R' :< n) = read . Text.unpack $ n
    readInstruction ('L' :< n) = negate . read . Text.unpack $ n
    readInstruction _ = undefined
    
    myName :: (Foldable t, Ord b, Enum b, Num b) => b -> t b -> b
    myName maxPosition = List.foldl' (\ pos offset -> min (pred maxPosition) . max 0 $ pos + offset) 0
    
    parentName1 :: [Int] -> Int
    parentName1 = List.sum
    
    newSTArray :: [e] -> ST s (STArray s Int e)
    newSTArray xs = MutableArray.newListArray (0, length xs - 1) xs
    
    swap :: (MutableArray.MArray a e m, MutableArray.Ix i) => a i e -> i -> i -> m ()
    swap array i0 i1 = do
      e0 <- MutableArray.readArray array i0
      e1 <- MutableArray.readArray array i1
      MutableArray.writeArray array i0 e1
      MutableArray.writeArray array i1 e0
    
    parentName2 :: [Text] -> [Int] -> Text
    parentName2 nameList instructions = runST $ do
      names <- newSTArray nameList
      arrayLength <- succ . snd <$> MutableArray.getBounds names
      forM_ instructions $ \ offset -> do
        let arrayOffset = offset `mod` arrayLength
        swap names 0 arrayOffset
      MutableArray.readArray names 0
    
    main :: IO ()
    main = do
      names <- commaSepLine
      _ <- TextIO.getLine
      instructions <- fmap readInstruction <$> commaSepLine
    
      let namesLength = length names
      print $ names !! myName namesLength instructions
      print . (names !!) . (`mod` namesLength) $ parentName1 instructions
      print $ parentName2 names instructions
    
  • source
  • [โ€“] 3 points 1 year ago* (last edited 1 year ago) (5 children)

    edit: I did the wrong quest

    I wasn't very happy with myself on this one. Part 1 wound up being simple enough, but part 2 took me for a loop. My disappointment was that I asked an AI for a hint, but instead it coded up a solution revolved around checking cycle dependency.

    I tried to at least redo it in a functional way, since I'm writing this in FSharp, but I ran into a bunch of hiccups around indexes and index tracking. That always costs me so much time. This took me forever and I'm near the bottom of the rankings haha.

    Overall, I wasn't terribly happy with how I did on this one. Oh, I wound up switching all of the types to BigInteger because I was lost as to what was happening. It turned out to be indexes and orderings, but I thought that maybe something was getting truncated.

        /// Map from (prev, curr) pair to position  
        type PositionMap = Map<BigInteger * BigInteger, BigInteger * BigInteger>  
        let rec findCycle (pairToNextPair: PositionMap) startPair currentPair acc =  
            if currentPair = startPair && List.length acc > 0 then  
                Some (List.rev acc)  
            else  
                match Map.tryFind currentPair pairToNextPair with  
                | None -> None  
                | Some nextPair -> 
                    findCycle pairToNextPair startPair nextPair (snd currentPair :: acc)  
    
        let rec eni2' score (n:BigInteger) (exp:BigInteger) (m:BigInteger) (pairMap: PositionMap) (scores:BigInteger list) iter =  
            if iter > exp then scores |> List.rev |> List.skip (max 0 (List.length scores - 5)) |> toBigInt  
            else  
                let newScore = (score * n) % m  
                let key = (score, newScore)  
                
                match Map.tryFind key pairMap with  
                | Some _ -> 
                    match findCycle pairMap key key [] with  
                    | Some cycle ->  
                        let remaining = int64 (exp - iter)  
                        let cycleValues = cycle  
                        let cycleLength = List.length cycleValues |> int64  
                        let scoresLength = List.length scores |> int64  
                        let totalLength = scoresLength + 1L + remaining // scores + newScore + remaining  
                        
                        let needCount = min 5L totalLength  
                        let startPos = max 0L (totalLength - needCount)  
                        
                        let scoresReversed = List.rev scores  
                        
                        let final5 = 
                            [startPos..totalLength - 1L]  
                            |> List.map (fun pos ->  
                                if pos < scoresLength then  
                                    // Position is in scores (scores is reversed, so oldest is at end)  
                                    scoresReversed.[int pos]  
                                elif pos = scoresLength then  
                                    // Position is newScore  
                                    newScore  
                                else  
                                    let cycleOffset = pos - scoresLength  
                                    let cyclePos = cycleOffset % cycleLength  
                                    cycleValues.[int cyclePos]  
                            )  
                        
                        // final 5 comes out in reverse order  
                        final5 |> List.rev |> toBigInt  
                    | None -> 
                        eni2' newScore n exp m (Map.add key ((newScore, (newScore * n) % m)) pairMap) (newScore::scores) (iter + BigInteger 1)  
                | None ->  
                    let nextPair = (newScore, (newScore * n) % m)  
                    eni2' newScore n exp m (Map.add key nextPair pairMap) (newScore::scores) (iter +  BigInteger 1)  
    
        let eni2 (n) (exp) (m) = eni2' (BigInteger 1) n exp m Map.empty [] (BigInteger 1)  
    
        let part2 (input: IDictionary<string, BigInteger> array)=  
            input  
            |> Array.map (fun line ->  
                //printfn $"""running line {line.AsEnumerable() |> Seq.map(fun kv -> kv.Key + "=" + kv.Value.ToString()) |> fun x -> String.Join(", ", x)}"""  
                let a = eni2 line["A"] line["X"] line["M"]  
                let b = eni2 line["B"] line["Y"] line["M"]  
                let c = eni2 line["C"] line["Z"] line["M"]  
                let ret = a + b + c  
                //printfn $"found {ret}"  
                ret  
            ) |> Seq.max  
    
        let part2Answer = 
            File.ReadAllLines "Inputs/Q01_P02.txt"  
            |> part1Lines  
            |> part2  
    
  • source
  • [โ€“] [S] 3 points 1 year ago

    Rust

    use log::debug;
    use std::collections::HashSet;
    
    use regex::Regex;
    
    #[derive(PartialEq, Eq, Hash, Clone)]
    struct Number(isize, isize);
    
    impl Number {
    
      fn add(self: &Number, b: &Number) -> Number {
        Number(self.0 + b.0, self.1 + b.1)
      }
    
      fn mul(self: &Number, b: &Number) -> Number {
        Number(self.0 * b.0 - self.1 * b.1, self.0 * b.1 + self.1 * b.0)
      }
    
      fn div(self: &Number, b: &Number) -> Number {
        Number(self.0 / b.0, self.1 / b.1)
      }
    }
    
    pub fn solve_part_1(input: &str) -> String {
      let re = Regex::new(r"A=\[(\d+),(\d+)\]").unwrap();
      let (_, [x, y]) = re.captures(input).unwrap().extract();
      let a = Number(x.parse().unwrap(), y.parse().unwrap());
      let mut res = Number(0, 0);
      for _ in 0..3 {
        res = res.mul(&res);
        res = res.div(&Number(10, 10));
        res = res.add(&a);
      }
      format!("[{},{}]", res.0, res.1)
    }
    
    pub fn solve_part_2(input: &str) -> String {
      let re = Regex::new(r"A=\[([-0-9]+),([-0-9]+)\]").unwrap();
      let (_, [x, y]) = re.captures(input).unwrap().extract();
      let a = Number(x.parse().unwrap(), y.parse().unwrap());
      let mut engraved_points = 0;
      let mut pts: HashSet<_> = HashSet::new();
      for i in 0..=100 {
        for j in 0..=100 {
          let pt = Number(a.0 + 10 * i, a.1 + 10 * j);
          let mut res = Number(0, 0);
          engraved_points += 1;
          pts.insert(pt.clone());
          for _ in 0..100 {
            res = res.mul(&res);
            res = res.div(&Number(100_000, 100_000));
            res = res.add(&pt);
            if res.0.abs() > 1_000_000 || res.1.abs() > 1_000_000 {
              engraved_points -= 1;
              pts.remove(&pt);
              break;
            }
          }
        }
      }
      for i in 0..=100 {
        debug!("{}", (0..=100).map(|j| if pts.contains(&Number(a.0 + 10*i, a.1 + 10*j)) { 'X' } else {'.'}).collect::<String>());
      }
      engraved_points.to_string()
    }
    
    pub fn solve_part_3(input: &str) -> String {
      let re = Regex::new(r"A=\[([-0-9]+),([-0-9]+)\]").unwrap();
      let (_, [x, y]) = re.captures(input).unwrap().extract();
      let a = Number(x.parse().unwrap(), y.parse().unwrap());
      let mut engraved_points = 0;
      for i in 0..=1000 {
        for j in 0..=1000 {
          let pt = Number(a.0 + i, a.1 + j);
          let mut res = Number(0, 0);
          engraved_points += 1;
          for _ in 0..100 {
            res = res.mul(&res);
            res = res.div(&Number(100_000, 100_000));
            res = res.add(&pt);
            if res.0.abs() > 1_000_000 || res.1.abs() > 1_000_000 {
              engraved_points -= 1;
              break;
            }
          }
        }
      }
      engraved_points.to_string()
    }
    
  • source
  • [โ€“] 5 points 1 year ago* (last edited 1 year ago)

    I like the idea of using this community for other similar seasonal challenges. By its nature AoC isn't going to be very active outside of December so this would be a nice way to have the community be active.

  • source
  • view more: โ€น prev