Quest 2: From Complex to Clarity

  • Keep top level comments as only solutions, if you want to say something other than a solution put it in a new post. (replies to comments can be whatever)
  • You can send code in code blocks by using three backticks, the code, and then three backticks or use something such as https://topaz.github.io/paste/ if you prefer sending it through a URL

Link to participate: https://everybody.codes/

  • ystael@beehaw.org
    link
    fedilink
    arrow-up
    2
    ·
    19 hours ago

    I was sad to discover that division wasn’t actual complex division. As in some other languages, one has to implement this “division” by hand because the library designers assume that you never want truncating integer division together with complex numbers. Which is reasonable, I guess.

    (ql:quickload :cl-ppcre)
    (ql:quickload :str)
    
    (defun parse-line (line)
      (ppcre:register-groups-bind
          (name x y)
          ("^([A-Za-z]+)=[[]([\-0-9.]+),([\-0-9.]+)[]]$" line)
        (cons name (complex (parse-integer x) (parse-integer y)))))
    
    (defun read-inputs (filename)
      (let ((input-lines (uiop:read-file-lines filename)))
        (parse-line (car input-lines))))
    
    (defun fake-complex-division (y d)
      (complex (truncate (realpart y) (realpart d))
               (truncate (imagpart y) (imagpart d))))
    
    (defun f (x a)
      (+ a (fake-complex-division (* x x) #C(10 10))))
    
    (defun main-1 (filename)
      (let* ((a (cdr (read-inputs filename)))
             (result (f (f (f #C(0 0) a) a) a)))
        (str:concat "["
                    (write-to-string (realpart result))
                    ","
                    (write-to-string (imagpart result))
                    "]")))
    
    (defun engrave? (p)
      (labels ((iter (x n)
                 (if (zerop n)
                     t
                     (let ((next-x (+ p (fake-complex-division (* x x) #C(100000 100000)))))
                       (if (or (> (abs (realpart next-x)) 1000000)
                               (> (abs (imagpart next-x)) 1000000))
                           nil
                           (iter next-x (- n 1)))))))
        (iter 0 100)))
    
    (defun count-engraves (a spacing)
      (let ((steps (/ 1000 spacing)))
        (loop for x from 0 to steps
              sum (loop for y from 0 to steps
                        for p = (+ a (complex (* spacing x) (* spacing y)))
                        sum (if (engrave? p) 1 0)))))
    
    (defun main-2 (filename)
      (count-engraves (cdr (read-inputs filename)) 10))
    
    (defun main-3 (filename)
      (count-engraves (cdr (read-inputs filename)) 1))
    
  • mykl@lemmy.world
    link
    fedilink
    arrow-up
    2
    ·
    1 day ago

    Sadly Uiua doesn’t handle the large numbers here well, so here’s a Dart solution instead.

    import 'dart:math';
    import 'package:more/more.dart';
    
    typedef P = Point;
    void main(List<String> args) {
      P add(P a, P b) => P(a.x + b.x, a.y + b.y);
      P mul(P a, P b) => P(a.x * b.x - a.y * b.y, a.x * b.y + a.y * b.x);
      P div(P a, P b) => P(a.x ~/ b.x, a.y ~/ b.y);
      P part1(P a) {
        P r = P(0, 0);
        for (var _ in 0.to(3)) {
          r = add(div(mul(r, r), P(10, 10)), a);
        }
        return r;
      }
    
      bool test2(P a) {
        P r = P(0, 0);
        for (var _ in 0.to(100)) {
          r = add(div(mul(r, r), P(100000, 100000)), a);
          if (r.x.abs() > 1000000 || r.y.abs() > 1000000) return false;
        }
        return true;
      }
    
      part2(P a, {step = 1}) => [
            for (var x in 0.to(1001, step: step))
              for (var y in 0.to(1001, step: step)) test2(P(a.x + x, a.y + y))
          ].count((e) => e);
    
      print(part1(P(25, 9)));
      print(part2(P(35300, -64910), step: 10));
      print(part2(P(35300, -64910)));
    }
    
  • vole@lemmy.world
    link
    fedilink
    English
    arrow-up
    3
    ·
    4 days ago

    I was stuck for a while on part 2. I thought I was running into precision errors, so I re-implemented everything using exact integers, then I re-implemented it in python, but it turns out that I just had an off-by-1 error. It just so happened that my off-by-1 algorithm gave the correct solution to the sample input. Part 3 takes a while to compute.

    Scheme/Guile

    (use-modules (ice-9 rdelim))
    (use-modules (ice-9 format))
    (use-modules (srfi srfi-1))
    
    (define (parse-file file-name)
      (let* ((p (open-input-file file-name))
            (equality-split (string-split (read-line p) #\=))
            (complex-str (list-ref equality-split 1))
            (complex-parts (map string->number (string-split
              (substring/read-only complex-str 1 (- (string-length complex-str) 1))
              #\,))))
        (+ (car complex-parts) (* 0+1i (list-ref complex-parts 1)))))
    (define (special-div numerator divisor)
      (+
        (truncate/ (real-part numerator) (real-part divisor))
        (* 0+1i (truncate/ (imag-part numerator) (imag-part divisor)))))
    
    (let* ((A (parse-file "notes/everybody_codes_e2025_q02_p1.txt")))
      (format #t "Input is ~a\n" A)
      (let loop ((R 0+0i) (i 3))
        (if (> i 0)
          (loop (+ (special-div (* R R) 10+10i) A) (- i 1))
          (format #t "P1 Answer: [~d,~a]\n\n"
                  (inexact->exact (real-part R))
                  (inexact->exact (imag-part R))))))
    
    
    (define (check-engrave-range value)
      (and-map (lambda (x) (<= (abs x) 1000000)) (list (real-part value) (imag-part value))))
    (define (check-engrave point)
      (let loop ((result 0+0i) (i 100))
        (if (and (> i 0) (check-engrave-range result))
          (loop (+ point (special-div (* result result) 100000+100000i)) (- i 1))
          (check-engrave-range result))))
    (let* ((origin (parse-file "notes/everybody_codes_e2025_q02_p2.txt"))
           (engrave-count 0))
      (format #t "Input is ~a\n" origin)
      (let loop ((i 0))
        (if (<= i 1000) (begin
          (let loop ((j 0))
            (if (<= j 1000) (begin
              (set! engrave-count (+ engrave-count
                                     (if (check-engrave (+ origin (+ i (* j 0+1i)))) 1 0)))
              (loop (+ j 10)))))
          (loop (+ i 10)))))
      (format #t "P2 answer: ~a\n\n" engrave-count))
    
    
    (let* ((origin (parse-file "notes/everybody_codes_e2025_q02_p3.txt"))
           (engrave-count 0))
      (format #t "Input is ~a\n" origin)
      (let loop ((i 0))
        (if (<= i 1000) (begin
          (let loop ((j 0))
            (if (<= j 1000) (begin
              (set! engrave-count
                    (+ engrave-count (if (check-engrave (+ origin (+ i (* j 0+1i)))) 1 0)))
              (loop (+ j 1)))))
          (loop (+ i 1)))))
      (format #t "P3 answer: ~a\n\n" engrave-count))
    
  • VegOwOtenks@lemmy.world
    link
    fedilink
    arrow-up
    3
    ·
    edit-2
    6 days ago

    I struggled for a long time because I had nearly the correct results. I had to switch div with quot.

    This puzzle was fun. If you have a visualization, it’s even cooler. (It’s a fractal)

    Haskell Code
    {-# LANGUAGE LambdaCase #-}
    {-# LANGUAGE PatternSynonyms #-}
    {-# OPTIONS_GHC -Wall #-}
    module Main (main) where
    import Text.Read (ReadPrec, Read (readPrec))
    import Data.Functor ((<&>))
    import Data.Text (pattern (:<), Text)
    import qualified Data.Text as Text
    import qualified Data.Text.IO as TextIO
    import Control.Monad ((<$!>))
    import Control.Arrow ((<<<))
    
    newtype Complex = Complex (Int, Int)
    
    instance Read Complex where
      readPrec :: ReadPrec Complex
      readPrec = readPrec <&> \case
        [a, b] -> Complex (a, b)
        _ -> undefined
    
    instance Show Complex where
      show :: Complex -> String
      show (Complex (a, b))= show [a, b]
    
    readAEquals :: Text -> Complex
    readAEquals ('A' :< '=':< rest) = read $ Text.unpack rest
    readAEquals _ = undefined
    
    
    -- >>> Complex (1, 1) `add` Complex (2, 2)
    -- [3,3]
    
    add :: Complex -> Complex -> Complex
    (Complex (x1, y1)) `add` (Complex (x2, y2)) = Complex (x1 + x2, y1 + y2)
    
    -- >>> Complex (2, 5) `times` Complex (5, 7)
    -- [-25,-11]
    
    times :: Complex -> Complex -> Complex
    (Complex (x1, y1)) `times` (Complex (x2, y2)) = Complex (x1 * x2 - y1 * y2, x1 * y2 + x2 * y1)
    
    dividedBy :: Complex -> Complex -> Complex
    (Complex (x1, y1)) `dividedBy` (Complex (x2, y2)) = Complex (x1 `quot` x2, y1 `quot` y2)
    
    step :: Complex -> Complex -> Complex
    step a r = let
     r1 = r `times` r
     r2 = r1 `dividedBy` Complex (10, 10)
     r3 = r2 `add` a
     in r3
    
    zero :: Complex
    zero = Complex (0, 0)
    
    part1 :: Complex -> Complex
    part1 a = iterate (step a) (Complex (0, 0)) !! 3
    
    shouldBeEngraved :: Complex -> Bool
    shouldBeEngraved complexPoint = let
    
      cycleStep :: Complex -> Complex -> Complex
      cycleStep point r = let
        r2 = r `times` r
        r3 = r2 `dividedBy` Complex (100000, 100000)
        in point `add` r3
    
      inRange x = x <= 1000000 && x >= -1000000
    
    
      in all (\ (Complex (x, y)) -> inRange x && inRange y)
        <<< take 101
        <<< iterate (cycleStep complexPoint)
        $ zero
    
    -- >>> shouldBeEngraved $ Complex (35630,-64880)
    -- True
    -- >>> shouldBeEngraved $ Complex (35460, -64910)
    -- False
    -- >>> shouldBeEngraved $ Complex (35630, -64830)
    -- False
    
    part2 :: Complex -> Int
    part2 (Complex (xA, yA)) = let
    
        xB = xA + 1000
        yB = yA + 1000
    
      in length . filter shouldBeEngraved $ do
        x <- [xA, xA+10.. xB]
        y <- [yA, yA+10.. yB]
        pure $ Complex (x, y)
    
    part3 :: Complex -> Int
    part3 (Complex (xA, yA)) = length . filter shouldBeEngraved $ do
      x <- [xA..xA+1000]
      y <- [yA..yA+1000]
      pure $ Complex (x, y)
    
    -- >>> [0, 10..100]
    -- [0,10,20,30,40,50,60,70,80,90,100]
    
    main :: IO ()
    main = do
      a <- readAEquals <$!> TextIO.getContents
      print $ part1 a
      print $ part2 a
      print $ part3 a
    

    My girlfriend is learning python, we are taking on the challenges together, today I may upload her solution:

    python
    A=[-3344,68783]
    R = [0, 0]
    B= [A[0]+1000, A[1]+1000]
    pointsengraved = 0
    cycleright = 0
    
    
    for i in range(A[1], B[1]+1):
        for j in range(A[0], B[0]+1):
            for k in range(100):
                R = [int(R[0] * R[0] - R[1] * R[1]), int(R[0] * R[1] + R[1] * R[0])]
                R = [int(R[0] / 100000), int(R[1] / 100000)]
                R = [int(R[0] + j), int(R[1] + i)]
                if -1000000>R[0] or R[0]>1000000 or -1000000>R[1] or R[1]>1000000:
                    #print(".", end="")
                    break
                cycleright += 1
            if cycleright == 100:
                pointsengraved += 1
                #print("+", end="")
            cycleright = 0
            R = [0, 0]
        #print()
    
    print(pointsengraved)
    

    The commented out print statements produce an ascii map of the set, which can be cool to view at the right font size.

  • Amy@piefed.blahaj.zone
    link
    fedilink
    English
    arrow-up
    4
    ·
    edit-2
    9 days ago

    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  
    
  • janAkali@lemmy.sdf.org
    link
    fedilink
    arrow-up
    3
    ·
    8 days ago

    For quest 2, I decided to implement my own Complex type and operators, because I expected parts 2 and 3 to have something unconventional, but alas it’s just a regular Mandelbrot fractal.

    Nim again, Nim forever:

    type Complex = tuple[x,y: int]
    proc `$`(c: Complex): string = &"[{c.x},{c.y}]"
    proc `+`(a,b: Complex): Complex = (a.x+b.x, a.y+b.y)
    proc `-`(a,b: Complex): Complex = (a.x-b.x, a.y-b.y)
    proc `*`(a,b: Complex): Complex = (a.x*b.x - a.y*b.y, a.x*b.y + a.y*b.x)
    proc `/`(a,b: Complex): Complex = (a.x div b.x, a.y div b.y)
    proc `/`(a: Complex, b: int): Complex = (a.x div b, a.y div b)
    
    proc parseInput(input: string): Complex =
      let parts = input.split({'=','[',',',']'})
      (parseInt(parts[2]), parseInt(parts[3]))
    
    proc isStable(point: Complex, iter: int): bool =
      var num: Complex
      for _ in 1..iter:
        num = (num * num) / 100_000 + point
        if num.x notin -1_000_000 .. 1_000_000 or
           num.y notin -1_000_000 .. 1_000_000:
          return false
      true
    
    proc solve_part1*(input: string): Solution =
      let start = parseInput(input)
      var point: Complex
      for _ in 1..3:
        point = (point * point) / 10 + start
      result := $point
    
    proc solve_part2*(input: string): Solution =
      let start = parseInput(input)
      for y in 0..100:
        for x in 0..100:
          let point: Complex = (start.x + 10 * x, start.y + 10 * y)
          if point.isStable(iter=100): inc result.intVal
    
    proc solve_part3*(input: string): Solution =
      let start = parseInput(input)
      for y in 0..1000:
        for x in 0..1000:
          let point: Complex = (start.x + x, start.y + y)
          if point.isStable(iter=100): inc result.intVal
    

    Full solution at Codeberg: solution.nim

  • ragingHungryPanda@piefed.keyboardvagabond.com
    link
    fedilink
    English
    arrow-up
    3
    ·
    9 days ago

    FSharp
    (my first submission failed, so I may not be as detailed here)

    I laugh at how long this took me. At first I got stuck due to checked arithmetic. In FSharp you open the Checked module and you get the overloaded operators for primitives, whereas CSharp is checked {a + b}. I also mistakenly used distinct on part 3 when I didn’t need it.

    I did appreciate the choose functions of functional programming, which returns all of the Some values of a sequence of options. The PSeq library let me use 70% of the cpu on the calculations :)

    module EveryBodyCodes2025.Quest02  
    
    open System.IO  
    open System.Text.RegularExpressions  
    open FSharp.Collections.ParallelSeq  
    open Checked  
    
    [<Struct>]  
    type ComplexNumber = 
        {X: int64; Y: int64}  
        static member (+) (a: ComplexNumber, b: ComplexNumber) = {X = a.X + b.X; Y = a.Y + b.Y }  
        static member (*) (a: ComplexNumber, b: ComplexNumber) = { X = ( a.X * b.X ) - (a.Y * b.Y); Y = (a.X * b.Y) + (a.Y * b.X) }  
        static member (/) (a: ComplexNumber, b: ComplexNumber) = { X = a.X / b.X; Y = a.Y / b.Y }  
        override this.ToString() = $"[{this.X},{this.Y}]"  
        
    let parseInput file =  
        File.ReadAllLines file  
        |> fun lines ->  
            let reg = Regex(@"A=\[(-?\d+),(-?\d+)\]")  
            let res = reg.Match(lines[0])  
            match res.Success with  
            | true ->  
                let x = int64 res.Groups[1].Value  
                let y = int64 res.Groups[2].Value  
                {X = x; Y = y}  
            | false -> failwith "failed to parse"  
            
        
    let part1 () =  
        parseInput "Inputs/Quest02/Q02_P01.txt"  
        |> fun a ->  
            [1..3]  
            |> List.fold (fun acc _ -> (acc * acc) / { X = 10; Y = 10 } + a ) {X = 0; Y = 0}  
    
    let cycle p =  
        let rec iterate current iteration =  
            if iteration = 100 then Some current  
            else  
                let next = (current * current) / {X = 100_000; Y = 100_000 } + p  
                if abs(next.X) > 1_000_000 || abs(next.Y) > 1_000_000 then  
                    None  
                else iterate next (iteration + 1)  
        iterate { X = 0; Y = 0 } 0  
    
    let part2 () =  
        parseInput "Inputs/Quest02/Q02_P02.txt"  
        |> fun a ->  
            seq {  
                for y in 0..100 do  
                for x in 0..100 do  
                    yield {X = a.X + (int64 x * 10L); Y = a.Y + (int64 y * 10L)}  
            }  
            |> PSeq.choose cycle  
            |> PSeq.distinct  
            |> PSeq.length  
    
    let part3 () =  
        parseInput "Inputs/Quest02/Q02_P03.txt"  
        |> fun a ->  
            seq {  
                for y in 0..1000 do  
                for x in 0..1000 do  
                    yield {X = a.X + (int64 x); Y = a.Y + (int64 y)}  
            }  
            |> PSeq.choose cycle  
            |> PSeq.length  
    
  • hades@programming.devOPM
    link
    fedilink
    arrow-up
    3
    ·
    10 days 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()
    }