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/
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() }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
divandquotI 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) 67997Then I forgot which of div and quot I should use, and kept getting nearly the right answer
That sounds amazingly infuriating! and hard to debug. I totally feel you there.
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
Checkedmodule and you get the overloaded operators for primitives, whereas CSharp ischecked {a + b}. I also mistakenly useddistincton part 3 when I didn’t need it.I did appreciate the
choosefunctions of functional programming, which returns all of theSomevalues 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.lengthFor 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.intValFull solution at Codeberg: solution.nim
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))




