From c2ea2ec16e932ebbcbd448e8cde2bb6489832683 Mon Sep 17 00:00:00 2001 From: efim Date: Fri, 16 Dec 2022 15:30:38 +0000 Subject: [PATCH] day 15 part 2. using INTERVALS --- day15-scratch.lisp | 296 +++++++++++++++++++++++++++++++++++++++++++++ day15.lisp | 39 +++++- 2 files changed, 334 insertions(+), 1 deletion(-) diff --git a/day15-scratch.lisp b/day15-scratch.lisp index 7561a4f..944bdf6 100644 --- a/day15-scratch.lisp +++ b/day15-scratch.lisp @@ -183,3 +183,299 @@ *day15-sensors-list*))) ;; ok. + +;; +;; new idea: +;; have class for "not intersecting intervals" +;; with method to add ( ? remove ) new interval + +;; in part 2 we're looking for points which are outside of all scanners +;; where "last beacon" can be + +;; start of idea - distance goes by x and y simmetrically. +;; between line Y1 and beacon (X2 Y2) we can calculate Y2 - Y1, +;; if that is > than length covered - then knowingly 0 points covered by scanners +;; if htat is < that covered length : abs(Y2 - Y1) = diff +;; that diff will be covered into both sides to the left and to the right of the X2 +;; (Y1 X2) will be exactly diff distance away. +;; so (length - diff) is by how much we can go to the left and right and still be with distance to beacon upto length +;; Interval [(x2-diff, y1) .. (x2+diff, y1)] are all points where "there can't be unkown beacons" +;; +;; and my idea is to operate on the point intervals. +;; start with "total interval" from 0 to 4M, i guess +;; then - for each beacon calculate Interval where "can't be unknown beacons" +;; and subtract them from the line +;; +;; without use of "per point" is best +;; so. want to have class, that stores non-intersecting intervals ( scala SortedMap would be good here ) +;; +;; but ok, can be just ((start end) (start2 end2)) sorted by #'first after every operation +;; what would be algo for "removing interval" +;; +;; go though the increasing 'interval starts', find first that's > our-start +;; then check current interval and previous interval +;; +;; previous interval could have end that clips current-interval +;; at-which-we-stopped could clip "end" of current-interval + +;; should i just have class for interval? nah, just method, since no need in type checking? + +(defun subtract-interval (minuend subtrahend) + (destructuring-bind ((m-left m-right) (s-left s-right)) (list minuend subtrahend) + (let ((resulting-interval + (if (< m-left s-left) + (list ; minuend starts to the left + m-left (min m-right s-left)) + (list ; minuend starts to the right + s-right m-right) + ))) + (when (<= (first resulting-interval) (second resulting-interval)) ; >= to allow intervals [4 4] + resulting-interval)))) + +(subtract-interval '(1 100) '(0 101)) ; NIL correct +(subtract-interval '(1 100) '(10 20)) ; only one return value, incorrect + +;; oh, but it can be that our subrahend fully devours "to the right" and we'd need to check "next to the right" +;; ugh +;; went to search and found 'cl-interval + +(ql:quickload 'cl-interval) +(interval:make-interval :start 1 :end 100 ) +;; this is not what i need? +(defparameter *some-tree* + (interval:make-tree )) +(interval:insert *some-tree* (interval:make-interval :start 1 :end 100) ) +(interval:delete *some-tree* (interval:make-interval :start 10 :end 20) ) +*some-tree* +(interval:find-all *some-tree* 11) ; nope deletion doesn't work like i want it + +;; ugh. write it on my own +(defstruct ([] (:constructor [] (low high))) + (low 0.0 :type real) + (high 0.0 :type real)) + +(defmethod sub ((i1 []) (i2 [])) + ([] (- ([]-low i1) ([]-high i2)) + (- ([]-high i1) ([]-low i2)))) + +(sub ([] 1 100) ([] 10 20)) ; ([] -19 90) that's bs + +;;; ugh. this is somethign completely different +;; so, back to my function +;; should be able to return list of intervals. either one or two if split +(defun subtract-interval (minuend subtrahend) + (destructuring-bind ((m-left m-right) (s-left s-right)) (list minuend subtrahend) + (cond + ((< m-right s-left) (list m-left m-right)) ; minuend fully to the left + ((> m-left s-right) (list m-left m-right)) ; minuend fully to the right + ((and (< m-left s-left) + (> m-right s-right)) ; minuend is around subtrahend + (list (list m-left (1- s-left)) + (list (1+ s-right) m-right))) ; part before and after subtrahend + ((and (>= m-left s-left) + (<= m-right s-right)) ; subtrahend consumes minuend + nil) + ((< m-left s-left) ; minuend start to the left, but not subtrahend consumes all right part + (list m-left (1- s-left))) + ((> m-right s-right) ; minuend has part to the right of subtrahend + (list (1+ s-right) m-right))))) + +(subtract-interval '(1 100) '(0 101)) ; NIL correct +(subtract-interval '(1 100) '(10 20)) ; two intervals, correct +(subtract-interval '(1 20) '(10 30)) ; correct, had deducted 10 +(subtract-interval '(10 30) '(1 20)) ; correct, had deducted 20 +(subtract-interval '(25 30) '(1 20)) ; correct, not changed +(subtract-interval '(1 20) '(25 30)) ; correct not changed +(subtract-interval '(1 20) nil) ; correct not changed + +;; ok. now what. have interval +'(0 4000000) ; and deduct from it found intervals. + ; it would produce list of intervals + ; so for each new interval - deduct from all + ; then i'll have list of intervals, where "unknown beacon is possible" + + +;; now. hm. +;; loop. no. first function that for LINE-Y and BEACON-CENTER calculates "no-unkown-beacons" interval + +(defun get-no-unknown-beacons-x-interval (line-y scanner) + (let* ((y-dist (abs (- line-y (y (self-coord scanner))))) + (x-slack (- (covered-dist scanner) y-dist)) + (x-sc (x (self-coord scanner)))) + (when (>= x-slack 0) + (list (- x-sc x-slack) (+ x-sc x-slack))))) + +*test-sensor* ; x: 2, y: 18, dist: 7 +(y (self-coord *test-sensor*)) + +(get-no-unknown-beacons-x-interval 18 *test-sensor*) +(get-no-unknown-beacons-x-interval 17 *test-sensor*) +(get-no-unknown-beacons-x-interval 19 *test-sensor*) +;; should be (-5 9) + +(get-no-unknown-beacons-x-interval 11 *test-sensor*) +(manh-dist (make-instance 'point :x 2 :y 11) (self-coord *test-sensor*)) +;; seems right + +(get-no-unknown-beacons-x-interval 4 (make-sensor 1 1 2 2)) +;; seems right + +(get-no-unknown-beacons-x-interval 2 *test-sensor*) +;; yup + +;; now. start with interval '(0 4000000) +;; list of that interval +;; when working on a line +;; get 'no-unknowns' interval for each scanner +;; then for each interval in the lists - +;; take it oud and put results of subtraction instead + +(defun subtract-from-all (intervals subtrahend) + (mapcan (lambda (interval) (subtract-interval interval subtrahend)) + intervals)) + +(subtract-from-all '((1 4000000)) '(5 15)) ; yay +(subtract-from-all '((1 10) (12 17) (20 25)) '(5 23)) ; yay +(subtract-from-all '((3 10) (12 17) (20 25)) '(1 40)) ; yay +(subtract-from-all '((3 10) (12 17) (20 25)) nil) ; yay + +;; now looping. +;; we fix line, then for each scanner we calculate interval and update our intervals +;; in the end - if not NIL - then some points can have "unknown beacond" + +;; let's figure out inner loop first +(defun line-unknown-intervals (y scanners max-x) + (do* + ((rest-scanners scanners (cdr rest-scanners)) + (scanner (first rest-scanners) (first rest-scanners)) + (known-interval (get-no-unknown-beacons-x-interval y scanner) + (when scanner (get-no-unknown-beacons-x-interval y scanner))) + (intervals (subtract-from-all `((0 ,max-x)) known-interval) + (subtract-from-all intervals known-interval))) + ((not scanner) intervals) + ;; (format t "step, ~a intervals, after removing ~a; from ~a ~%" intervals known-interval scanner) + )) + +(line-unknown-intervals 11 (get-sensors-list "day15-test.txt") 20) +(line-unknown-intervals 10 (get-sensors-list "day15-test.txt") 20) +;; 2: (SUBTRACT-FROM-ALL ((0 2) 14 4000000) (-3 3)) +;; why is that intervals get polluted +;; +;; anothre problem we don't include last scanner? +;; +;; and another problem. do we remove too little? +;; step, ((-40 11) (13 40)) intervals, after removing (12 12); from #, linked to: #, covering dist: 4> +;; for line y=10, dist 4, sensor at , no all ok + +;; so, proposed answer is x=14, y=11 +;; which sensor precludes that in my process? +;; step, ((-40 10) (14 40)) intervals, after removing (11 13); from #, linked to: #, covering dist: 4> +;; , linked to: #, covering dist: 4> +;; for y=11. dist is 3. so 12+-1 right? +(manh-dist (make-instance 'point :x 12 :y 14) + (make-instance 'point :x 14 :y 11)) +;; so here distance is 5. wtf. +;; so. y=11 +;; sensor at +;; we spend 3 12+-1 wtf +;; OOOH. it's (14 14) - meaning X is 14 +;; and Y is 11 +;; crap + + +(subtract-from-all '((1 4000000)) '(3 13)) ; yay +;; using (format t "step, ~a intervals, after removing ~a ~%" intervals known-interval) +;; inside of DO loop +(subtract-from-all '((0 10) (14 400000)) '(3 13)) ; whoa +(subtract-interval '(14 400000) '(3 13)) ; correct not changed +;; well that's because in the "all below" i return not list of list +;; hello type safety, man + + +(defparameter *day-15-2-ans* nil) +(setq *day-15-2-ans* + (let ((sensors (get-sensors-list "day15-input.txt"))) + (loop + for y from 1 to 4000000 + for y-unknown-intervals = (line-unknown-intervals y sensors 4000000) + when y-unknown-intervals collect (list y y-unknown-intervals) + when (= 0 (mod y 10000)) do (format t "in step ~a~%" y)))) +(print *day-15-2-ans*) +;; well, there are lots of "possible solutions", ugh + +(defparameter *day-15-2-test* nil) +(setq *day-15-2-test* + (let ((sensors (get-sensors-list "day15-test.txt"))) + (loop + for y from 0 to 20 + for y-unknown-intervals = (line-unknown-intervals y sensors 20) + when y-unknown-intervals collect (list y y-unknown-intervals) + when (= 0 (mod y 1000)) do (format t "in step ~a~%" y)))) +*day-15-2-test* + +;; so, i do find the answer, but also lots of NON ANSWERS: +'((11 ((14 14))) (12 ((3 3))) (13 ((2 4))) (14 ((1 5))) (15 ((0 6))) + (16 ((0 7))) (17 ((0 8))) (18 ((0 7))) (19 ((0 6))) (20 ((0 5)))) + +;; for example :x 3 :y 12 +;; it should have been thrown out. why not? which scanner should have covered it +(line-unknown-intervals 12 (get-sensors-list "day15-test.txt") 20) +;; for example (3 12) and (-2 15 #7) nope, ok +;; for example (3 12) and (8 7 #9) nope, ok + +;; i need to automate it. for all scanners, find what? closest? +(let ((p (make-instance 'point :x 3 :y 12))) + (loop + for scanner in (get-sensors-list "day15-test.txt") + collect (list (manh-dist (self-coord scanner) p) (covered-dist scanner)))) +;; so for 1st scanner, dist 7 and covered-dist is 7. +;; UGH +;; and +;; - step, ((0 20)) intervals, after removing (1 3); from #, linked to: #, covering dist: 7> +;; here it was all along + +(subtract-from-all '((0 20)) '(1 3 )) +;; maybe that's bug of first iteration of DO* or something +;; it would be in "non-covered interval" + + +;; maybe i don't remove enough? +;; i should remove interval where all points "covered by the sensor" + +;; do i want to draw that shit? +'(((-40 -8) (28 40)) ((-40 -7) (27 40)) ((-40 -6) (26 40)) ((-40 -5) (25 40)) + ((-40 -4) (24 40)) ((-40 -3) (23 40)) ((-40 -2) (22 40)) ((-40 -1) (23 40)) + ((-40 -2) (24 40)) ((-40 -3) (25 40)) ((-40 -4) (14 14) (26 40)) + ((-40 -3) (3 3) (27 40)) ((-40 -2) (2 4) (28 40)) ((-40 -1) (1 5) (29 40)) + ((-40 6) (28 40)) ((-40 7) (27 40)) ((-40 8) (26 40)) ((-40 7) (25 40)) + ((-40 6) (24 40)) ((-40 5) (24 40))) + +(defun draw-line-def (line-intervals) + (format t "!") + (do* + ((intervals line-intervals (cdr intervals)) + (prev-interval nil interval) + (interval (first intervals) (first intervals))) + ((not interval) nil) + ;; (format t "iteration int: ~a; prev: ~a" interval prev-interval) + (when (not prev-interval) + (dotimes (i (first interval)) + (format t "."))) + (when prev-interval + (dotimes (i (- (first interval) (second prev-interval))) + (format t "."))) + (dotimes (i (- (second interval) (first interval))) + (format t "#")) + ) + (format t "!") (terpri) +) + +(draw-line-def '((-40 -8) (28 40))) +;; ok, i have 'draw-line' +(loop for line-def in *day-15-2-test* do + (draw-line-def line-def)) +;; +;; let's yolo +(2175292 ((2335771 2335771))) +(+ (* 2335771) 2175292) +;; that didn't work diff --git a/day15.lisp b/day15.lisp index 317ba55..6052050 100644 --- a/day15.lisp +++ b/day15.lisp @@ -18,6 +18,10 @@ (and (= (x left) (x right)) (= (y left) (y right)))) +(defmethod manh-dist ((one point) (two point)) + (+ (abs (- (x one) (x two))) + (abs (- (y one) (y two))))) + (defclass sensor () ((self-coord :initarg :self :reader self-coord) @@ -93,7 +97,7 @@ do (format t "iterating for x:~a y:~a~%" x to-check-y)))))) (count-certainly-not-beacons "day15-test.txt") -(count-certainly-not-beacons "day15-input.txt") +;; (count-certainly-not-beacons "day15-input.txt") ;; well, that's just too slow ;; how do i rewrite it to make it faster? @@ -112,3 +116,36 @@ ;; nope, even if it were 5 minutes for 16mil, ;; so 2 minutes per 4 mil, then multiply by 4M - more than a day. ;; think better + +(defun subtract-interval (minuend subtrahend) + (if (not subtrahend) + (list minuend) ; list of one interval + (destructuring-bind ((m-left m-right) (s-left s-right)) (list minuend subtrahend) + (cond + ((< m-right s-left) + (list (list m-left m-right))) ; minuend fully to the left + ((> m-left s-right) + (list (list m-left m-right))) ; minuend fully to the right + ((and (< m-left s-left) + (> m-right s-right)) ; minuend is around subtrahend + (list (list m-left (1- s-left)) + (list (1+ s-right) m-right))) ; part before and after subtrahend + ((and (>= m-left s-left) + (<= m-right s-right)) ; subtrahend consumes minuend + nil) + ((< m-left s-left) ; minuend start to the left, but not subtrahend consumes all right part + (list (list m-left (1- s-left)))) ; list of one interval + ((> m-right s-right) ; minuend has part to the right of subtrahend + (list (list (1+ s-right) m-right))))))) + + +(defun get-no-unknown-beacons-x-interval (line-y scanner) + (let* ((y-dist (abs (- line-y (y (self-coord scanner))))) + (x-slack (- (covered-dist scanner) y-dist)) + (x-sc (x (self-coord scanner)))) + (when (>= x-slack 0) + (list (- x-sc x-slack) (+ x-sc x-slack))))) + +(defun subtract-from-all (intervals subtrahend) + (mapcan (lambda (interval) (subtract-interval interval subtrahend)) + intervals))