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/

  • hades ( hades@programming.dev ) OP
    link
    fedilink
    arrow-up
    3
    ·
    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()
    }
    
  • Amy ( lwhjp@piefed.blahaj.zone ) 
    link
    fedilink
    English
    arrow-up
    3
    ·
    1 year 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  
    
  • 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  
    
  • 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

  • ystael ( ystael@beehaw.org ) 
    link
    fedilink
    arrow-up
    2
    ·
    1 year 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))