adventofcode 2024-12-04

did anyone do day 3 without regex?

feels like the obvious solution, but if anyone took the time to do it in another way iโ€™d love to see it

I had contemplated instaparse, but I hadn't used it since the last time I worked on those elves' computer and didn't want to relearn it. I suspect I might need to brush up this year.

yeah. i want to have more fun but encoding the regex is way faster than whatever i would have done

ours are very similar

clerk looks fantastic

Yeah. Makes for really clean results, even though I'm doing little with it.

yeah. the regex engine does all of the fun work in this one. when i first opened the input i was nervous though

โ˜๏ธ 1

I did instaparse instead, it is technique called island parsing - you parse only the parts you understand and prefer it to parts you do not (water)

(def string-parser-2                                                                                                                                                                                                             
  (insta/parser                                                                                                                                                                                                                  
   " = (mul? / do / dont / )+                                                                                                                                                                                          
     = 'do()'                                                                                                                                                                                                                
     = \"don't()\"                                                                                                                                                                                                         
     = #'.'                                                                                                                                                                                                               
     = <'mul('> num <','> num <')'>                                                                                                                                                                                         
     = #'[0-9][0-9]?[0-9]?'                                                                                                                                                                                                 
"))                                                                                                                                                                                                                              
                                                                                                                                                                                                                                 
(string-parser-2 "xmul(2,4)&mul[3,7]!^don't()_mul(5,5)+mul(32,64](mul(11,8)undo()?mul(8,5))") 
=> ("2" "4" "don't()" "5" "5" "11" "8" "do()" "8" "5")                                                                               

๐Ÿ’ฏ 2

Oh clever. Interesting adding the catch all to the grammar there

This works (re-seq #"do\(\)|don't\(\)|mul\(\d+,\d+\)") for me

Ah! Thanks @jcada, that's just what I need to fixup my https://clojurians.slack.com/archives/C0GLTDB2T/p1733258940529699?thread_ts=1733203334.692819&cid=C0GLTDB2T with instaparse. [Edit: I celebrated too early. Tried your exact snippet and while it works with the example input, it can't handle the full input on my machine - heats up the room for a couple of minutes until I stop it] *I'm using

instaparse/instaparse  {:mvn/version "1.5.0"}
org.clojure/clojure    {:mvn/version "1.12.0"}
openjdk 21.0.5 2024-10-15

@jcada ended up doing much of the same using another lib, my grammar looked like:

{:puzzle-2-grammar-elt (/ :comment :puzzle-1-grammar-elt)
 :comment              ["don't()" ~(*| :ignored `(/ "do()" $))]

 :puzzle-1-grammar-elt (/ :mul-insn :ignored)
 :mul-insn             (:mul-insn ["mul(" :factor "," :factor ")"])

 :factor               (:factor [:digit (? :digit) (? :digit)])
 :digit                #"\d"
 :ignored              .}

@dankelieberkorb for my full input it all takes 400 msec - I was using older version of instaparse and clojure so I updated to yours, but it is same - so maybe I was just lucky with my full input complexity

only catch I almost forgot about was that my full input was multiline - so I just connected it to single line first

Thank you for checking! Looked at my input and found the culprit:it contains 4 newlines. If I remove them, the parsing works fine.

Hah ๐Ÿ˜„

= #'.|\n' or = #'(?m).'

That also fixes it.

๐Ÿ‘ 1

That's a relief, because I really enjoyed instaparse. Will totally overuse it now for a while ๐Ÿ™‚

I very much understand. ๐Ÿ™‚ Btw I prefer it slightly to plain regexp as you can build more complex things. Also with complex regexp I sometimes got into quite hard to debug situations.

I especially enjoyed insta/transform and how doing the actual calculation becomes just "folding down the tree". https://clojurians.slack.com/archives/C0GLTDB2T/p1733258940529699?thread_ts=1733203334.692819&cid=C0GLTDB2T for posterity.

Update: i fixed it, and pushed

Hmm, my part1 solution is coming up with 14 for the test data (correct answer is 18). I dont see anything wrong with my alg so I think I might be misunderstanding the criteria

Oh, I think I might know

Yeah, my bug is a misunderstanding how how re-seq would work in this situation

(re-seq #"XMAS|SAMX" "XMASAMXAMM")
=> ("XMAS")

i need a lookahead assertion

its juxt all the way down ๐Ÿ™‚ https://github.com/erdos/advent-of-code/blob/master/2024/day04.clj

๐Ÿ‘ 4
๐Ÿ‘๐Ÿป 1

@zelark, what are you using to get simple execution times?

I don't think there's much really wild and crazy with my solution today, so I'll be curious to see what others came up with. โ€ข Blog: https://github.com/abyala/advent-2024-clojure/blob/main/docs/day04.md โ€ข Source: https://github.com/abyala/advent-2024-clojure/blob/main/src/advent_2024_clojure/day04.clj

โค๏ธ 1

(def cards [[0 -1] [1  0] [0 1] [-1 0]])
(def diags [[-1 -1] [1 -1] [-1 1] [1 1]])

(defn offsets [pairs len]
  (map (fn [[x y]] (+ x (* y len))) pairs))

(defn matches-a [xs idx offsets]
  (reduce + (for [offset      offsets
                  [step test] [[1 \M] [2 \A] [3 \S]]
                  :let        [next (get xs (+ idx (* step offset)))]
                  :while      (= next test)
                  :when       (= step 3)]
              1)))

(defn matches-b [xs idx offsets]
  (let [xf (comp (map (fn [x] (+ idx x))) (map (fn [i] (get xs i))))
        xs (into [] xf offsets)]
    (if (#{"SSMM" "SMSM" "MMSS" "MSMS"} (apply str xs))
      1 0)))

(let [in (line-seq (java.io.BufferedReader. *in*))
      ln (inc (count (first in)))
      xs (into [] (mapcat (fn [s] (str s \.))) in)
      rs (range (count xs))
      xf (fn [f char offsets]
           (comp (filter (fn [idx] (= (get xs idx) char)))
                 (map    (fn [idx] (f xs idx offsets)))))]
  (println "Part A:" (transduce (xf matches-a \X (offsets (into cards diags) ln)) + rs))
  (println "Part B:" (transduce (xf matches-b \A (offsets diags ln)) + rs)))
Added padding to the right and flattened the board, otherwise straight forward grid hopping

1

I use re-seq to search in -|\/ four directions for part1. To get \/ array of strings I rotate the array by 45 degrees.

I am currently managing to find too much xmas

Are you wrapping around the board? ๐Ÿ™‚

S M A M S Did you include these? I did and they should be excluded.

nope, just looking at horizontals, verticals and diagonals

So that's not how I ended up doing it... but I can't resist showing you how I did it first in part 1... Basically rotating the grid 45, 90 and 135 degrees and doing regexes on the lines. It's... a thing.

๐Ÿคฏ 4

It works though!

fwiw I had the same idea; the meat is in:

(defn views [hview]
  (let [vview (util/zipv hview)
        vview-rev (mapv #(vec (rseq %)) vview)
        h (count hview) w (count vview)
        diag (fn [v r0 c0]
               (let [at (fn [r c] (get (get v r) c))]
                 (into [] (comp (map #(at (+ r0 %) (+ c0 %)))
                                (take-while some?))
                       (range))))
        ddview (vec (concat (map #(diag hview 0 %) (range w))
                            (map #(diag hview % 0) (range 1 h))))
        rdview (vec (concat (map #(diag vview-rev 0 %) (range w))
                            (map #(diag vview-rev % 0) (range 1 h))))]
    [hview vview ddview rdview]))
which seems to work on the account of:
(views [[0 1 2]
        [3 4 5]
        [6 7 8]])
; => 
[[[0 1 2] [3 4 5] [6 7 8]] 
 [[0 3 6] [1 4 7] [2 5 8]] 
 [[0 4 8] [1 5] [2] [3 7] [6]] 
 [[6 4 2] [3 1] [0] [7 5] [8]]]
E: just submitted again, the issue was the regex ๐Ÿ˜…

Here's mine for today. A bit verbose (no time to refactor today) compared to others but no wrong guesses today ๐Ÿ˜„ I'm blown away at the simplicity of everyone's solutions!

;; ## Day 4
(def puzzle
  (->> ( 4)
       (clojure.string/split-lines)
       (map vec)
       (vec)))

(def test-puzzle
  (->> "MMMSXXMASM
MSAMXMSMSA
AMXSXMAAMM
MSAMASMSMX
XMASAMXAMM
XXAMMXXAMA
SMSMSASXSS
SAXAMASAAA
MAMMMXMMMM
MXMXAXMASX"
       (clojure.string/split-lines)
       (map vec)
       (vec)))

;; ### Part 1
(defn coordinate-seq
  ([grid]
   (coordinate-seq (constantly true) grid))
  ([pred grid]
   (remove nil?
           (apply concat
                  (for [i (range (count grid))]
                    (for [j (range (count (get grid i)))]
                      (when (pred (get-in grid [i j]))
                        [i j])))))))

(defn transpose
  [grid]
  (apply map vector grid))

(defn paths-around
  [[x y] n]
  (transpose
   (for [i (range n)]
     [[(+ x i) y]
      [(- x i) y]
      [x (+ y i)]
      [x (- y i)]
      [(+ x i) (+ y i)]
      [(+ x i) (- y i)]
      [(- x i) (+ y i)]
      [(- x i) (- y i)]])))

(defn out-of-bounds?
  [[x y] n m]
  (not (and (< -1 x n)
            (< -1 y m))))

(defn valid-path?
  [path n m]
  (not (some #(out-of-bounds? % n m) path)))

(defn path-to-seq
  [grid path]
  (map #(get-in grid %) path))

(defn xmas-count-at
  [puzzle coordinate]
  (->> (paths-around coordinate 4)
       (filter #(valid-path? % 140 140))
       (map #(path-to-seq puzzle %))
       (map #(apply str %))
       (filter #(= "XMAS" %))
       (count)))

(defn D4P1 [puzzle]
  (transduce
   (map (partial xmas-count-at puzzle))
   + 
   (coordinate-seq #{\X} puzzle)))

(tests
  (coordinate-seq [[1 2] [3 4]]) := '([0 0] [0 1] [1 0] [1 1])
  (coordinate-seq #{2 4} [[1 2] [3 4]]) := '([0 1] [1 1])
  (paths-around [0 0] 2) := '([[0 0] [1 0]]
                              [[0 0] [-1 0]]
                              [[0 0] [0 1]]
                              [[0 0] [0 -1]]
                              [[0 0] [1 1]]
                              [[0 0] [1 -1]]
                              [[0 0] [-1 1]]
                              [[0 0] [-1 -1]])
  (out-of-bounds? [0 0] 1 1) := false
  (out-of-bounds? [-1 0] 1 1) := true
  (out-of-bounds? [0 -1] 1 1) := true
  (path-to-seq test-puzzle [[0 0] [0 1] [0 2]]) := '(\M \M \M)
  (D4P1 test-puzzle) := 18)

(D4P1 puzzle)

;; ### Part 2
(defn x-mas-count-at
  [puzzle coordinate]
  (let [[i j] coordinate
        top-left     (get-in puzzle [(dec i) (dec j)])
        top-right    (get-in puzzle [(dec i) (inc j)])
        bottom-left  (get-in puzzle [(inc i) (dec j)])
        bottom-right (get-in puzzle [(inc i) (inc j)])
        target #{\M \S}
        found? (and (= target (set [top-left bottom-right]))
                    (= target (set [top-right bottom-left])))]
    (if found? 1 0)))

(defn D4P2 [puzzle]
  (transduce
   (map (partial x-mas-count-at puzzle))
   + 
   (coordinate-seq #{\A} puzzle)))

I'm trying to solve it with regular expressions but I don't get the matches I expect for the sample input, without too much hand holding, what am I missing?

(defn build-patterns
  [sample]
  (let [width (dec (String/.indexOf sample "\n"))
        pad (format ".{%s}" width)
        lpad (format ".{%s}" (inc width))
        rpad (format ".{%s}" (dec width))
        samx (apply str (reverse "XMAS"))]
    (into
     {}
     (map (fn [xs]
            (let [p (apply str xs)]
              [p (re-pattern p)])))
     ["XMAS"
      samx
      (interpose pad "XMAS")
      (interpose pad samx)

      (interpose lpad "XMAS")
      (interpose lpad samx)
      (interpose rpad "XMAS")
      (interpose rpad samx)])))


(defn apply-patterns
  [sample patterns]
  (reduce-kv (fn [m k v]
               (assoc m k (re-seq v sample))) {} patterns))

(defn go
  [sample]
  (apply-patterns (str/replace sample "\n" "") (build-patterns sample)))

And finally (I kept uploading an unsaved version):

https://github.com/benjamin-asdf/advent-of-code/blob/master/src/Y2024/day4.clj tried to make a cellular automaton (CA) work. Probably has obvious flaws, if anybody knows about CA's I'm interested in the differences between this and better solutions. Worked on perf and part 2 is sub 10ms. Is anybody faster ?๐Ÿ˜›

๐Ÿš€ 1

what is CA ? constraint analysis?

@misha cellular automaton

๐Ÿค 1

@benjamin.schwerdtner with zero perf work d4p2

(time
 (dotimes [_ 1e3]
   (solve2 input)))
"Elapsed time: 10404.654548 msecs"
about 10.4 ms/iter. Shall we play this game? ๐Ÿ˜›

๐Ÿ˜„ 1

@ben.sless nice. Can you elaborate on your runtime env?

205ms for part 1; 71ms for part 2

Just regular clojure 1.12, Java 17, PopOS 24 (basically ubuntu), AMD processor

Replacing one get-in with an unrolled get already puts me in sub 10ms

What's the average of three iterations rather than 1E3?

On a freshly started JVM. Wondering how much Jit joy you got with 1,000 iterations

ohh, I get it, hang on, I'll check

I'm no expert in this but that did come to mind

Three iterations ran 10 times consecutively

"Elapsed time: 45.611949 msecs"
"Elapsed time: 48.395905 msecs"
"Elapsed time: 37.158043 msecs"
"Elapsed time: 25.569873 msecs"
"Elapsed time: 27.073634 msecs"
"Elapsed time: 25.288856 msecs"
"Elapsed time: 25.312071 msecs"
"Elapsed time: 26.871498 msecs"
"Elapsed time: 25.300318 msecs"
"Elapsed time: 25.515188 msecs"

Seems that by 10 iterations JIT compilation has done most of what it can

๐Ÿ‘ 2

@ben.sless Let's play

(time
  (dotimes [_ 1e3]
  (p2 input)))
"Elapsed time: 7991.399875 msecs"
came around to 7.99 per iteration

๐Ÿ‘ 1

Can you DM me the code you're running?

Also yours Manuel? ๐Ÿ˜

it's pretty far from a clever solution, but it's fast

@manuelsantana Since we're not on the same hardware or JVM version it's probably meaningless, but 7.4 ๐Ÿ˜›

$ ys -C manuel.clj 
"Elapsed time: 27.24031 msecs"
$ bb manuel.clj 
"Elapsed time: 29.885733 msecs"
ys is a SCI based runtime like bb

I just wanted to compare where I was at with 43ms...

Replacing get on a string with charAt() puts me at under 4ms

๐Ÿ˜ฒ 1
3

@ingy Clojure is very polymorphic and will do its best to make the function and type you give it play nice together. It may not be fast

See this implementation of get

probably a better idea to call nth on a string than get

Use nth on the vector, too ๐Ÿ™ƒ

With less polymorphic solution, from cold start:

"Elapsed time: 26.980475 msecs"
"Elapsed time: 16.551328 msecs"
"Elapsed time: 13.212523 msecs"
"Elapsed time: 12.624409 msecs"
"Elapsed time: 9.941505 msecs"
"Elapsed time: 9.984618 msecs"
"Elapsed time: 9.890969 msecs"
"Elapsed time: 11.583621 msecs"
"Elapsed time: 9.792882 msecs"
"Elapsed time: 9.68175 msecs"

3 seconds for 1e3 iterations. That's not very fast btw

You mean you improved performance by using nth? what were you using before?

@ingy Clojure is very polymorphic and will do its best to make the function and type you give it play nice together. It may not be fast
@ben.sless What's that in response to specifically?

@manuelsantana get, I was lazy @ingy your ๐Ÿ˜ฒ reaction. I thought I'd elaborate a bit on the cause

โœ… 1

I really had no idea get was slower til

It's not slower, it depends. get from a vector or string is slower than nth.

It doesn't matter unless you're doing lots of get-ing

> your ๐Ÿ˜ฒ reaction @ben.sless I was pretty sure you meant that but also thought you might have been referring to what I said yesterday which deals with yamlscript's polymorphism (which happens on a higher level, takes poly a lot further, is more expensive, but also has ways to avoid it) which is focussed first on clean code in a yaml context. For example:

$ ys -ce 'a + b'
(add+ a b)
$ ys -ce '(+ a b)'
(+ a b)
$ ys -ce 'a(+ b)'
(a + b)
a + b can work on a lot of clojure types, like strings, maps, seqables... and
$ ys -ce 'a.take(b)'
(+take a b)
$ ys -ce 'a.take(b _)'
(take b a)
where . is a kind of thread-"smart" operator that does runtime checking unless you use _ to place the LHS explicitly.
$ ys -pe '-"foobarbaz".take(5)'
(\f \o \o \b \a)
$ ys -pe '5.take("foobarbaz")'
(\f \o \o \b \a)

otoh (I'd say) clojure is polymorphic where it makes most sense to be from a java interop perspective. different goals...

I'm still on the fence about how far to push nil punning ๐Ÿ˜„

As usual on my phone in the Ape CLJS playground: https://jurjanpaul.github.io/ape-cljs-playground/?code=KGRlZiBpbnB1dCAiTU1NU1hYTUFTTQpNU0FNWE1TTVNBCkFNWFNYTUFBTU0KTVNBTUFTTVNNWApYTUFTQU1YQU1NClhYQU1NWFhBTUEKU01TTVNBU1hTUwpTQVhBTUFTQUFBCk1BTU1NWE1NTU0KTVhNWEFYTUFTWCIpCgooZGVmbiBpbmRleGVkIFtjb2xsXQogIChtYXAtaW5kZXhlZCB2ZWN0b3IgY29sbCkpCgooZGVmbiBwYXJzZS1ncmlkIFtzXQogICgtPj4gKGZvciBbW3kgcm93XSAoaW5kZXhlZCAoc3RyaW5nL3NwbGl0LWxpbmVzIHMpKQogICAgICAgICAgICAgW3ggY10gKGluZGV4ZWQgcm93KV0KICAgICAgICAgW1t4IHldIGNdKQogICAgICAgKGludG8ge30pKSkKCihkZWZuIHYrIFsmIHZzXQogIChhcHBseSBtYXAgKyB2cykpCgooZGVmIHBhcnNlZAogICgtPj4gaW5wdXQKICAgICAgIHBhcnNlLWdyaWQpKQogICAgICAgCihkZWZuIGRpcmVjdGlvbi1tYXRjaD8KICBbZ3JpZCBkIHMgeHldCiAgKG9yIChlbXB0eT8gcykKICAgICAgKGFuZCAoPSAoZmlyc3QgcykgCiAgICAgICAgICAgICAgKGdldCBncmlkIHh5KSkKICAgICAgICAgICAocmVjdXIgZ3JpZAogICAgICAgICAgICAgICAgICBkCiAgICAgICAgICAgICAgICAgIChuZXh0IHMpIAogICAgICAgICAgICAgICAgICAodisgeHkgZCkpKSkpCgooZGVmIGRpcmVjdGlvbnMKICBbWzEgMF0gCiAgIFsxIDFdCiAgIFswIDFdCiAgIFstMSAxXQogICBbLTEgMF0KICAgWy0xIC0xXQogICBbMCAtMV0KICAgWzEgLTFdXSkKCihkZWZuIHhtYXNlcwogIFtncmlkIFt4eSBfXV0KICAoLT4%2BIChmaWx0ZXIgIyhkaXJlY3Rpb24tbWF0Y2g%2FIGdyaWQgJSAiWE1BUyIgeHkpCiAgICAgICAgICAgICAgIGRpcmVjdGlvbnMpCiAgICAgICBjb3VudCkpCiAgICAgICAKKGRlZm4gcGFydDEgW10KICAobGV0IFt4cyAoZmlsdGVyIChmbiBbW3h5IGNdXSAoPSAiWCIgYykpCiAgICAgICAgICAgICAgICAgICBwYXJzZWQpXQogICAgKC0%2BPiAobWFwIChwYXJ0aWFsIHhtYXNlcyBwYXJzZWQpCiAgICAgICAgICAgICAgeHMpCiAgICAgICAgIChyZWR1Y2UgKykpKSkKICAgICAgIAoocHByaW50L3BwcmludCAodGltZSAocGFydDEpKSkKCihkZWYgZGlhZ29uYWxzCiAgW1sxIDFdCiAgIFstMSAxXQogICBbLTEgLTFdCiAgIFsxIC0xXV0pCgooZGVmbiBtYXMtQS1jb29yZHMKICBbZ3JpZCBbeHkgX11dCiAgKC0%2BPiAoZmlsdGVyICMoZGlyZWN0aW9uLW1hdGNoPyBncmlkICUgIk1BUyIgeHkpCiAgICAgICAgICAgICAgIGRpYWdvbmFscykKICAgICAgIChtYXAgIyh2KyB4eSAlKSkpKQogICAKKGRlZm4gcGFydDIgW10KICAobGV0IFttcyAoZmlsdGVyIChmbiBbW3h5IGNdXSAoPSAiTSIgYykpCiAgICAgICAgICAgICAgICAgICBwYXJzZWQpXQogICAgICgtPj4gKG1hcGNhdCAocGFydGlhbCBtYXMtQS1jb29yZHMgcGFyc2VkKQogICAgICAgICAgICAgICAgICBtcykKICAgICAgICAgIGZyZXF1ZW5jaWVzCiAgICAgICAgICAoZmlsdGVyIChmbiBbW3h5IG51bWJlcl1dCiAgICAgICAgICAgICAgICAgICAgKD0gMiBudW1iZXIpKSkKICAgICAgICAgIGNvdW50KSkpCiAgCihwcHJpbnQvcHByaW50ICh0aW1lIChwYXJ0MikpKQo%3D&amp;checksum=LTk5NjE5Mzc3Mg%3D%3D

What seems specifically different (naive or blissfully lazy) in my solution (above) from the others is that in part 2 I just count the As that appear in 2 findings of MAS.

@jurjanpaul502 me too, I used frequencies of course ^_^

@zelark Ah, yes, I see! (Overlooked that earlier, sorry.)

I really hate my solution but it works

(def dirs (for [x [-1 0 1]
                y [-1 0 1]
                :when (not (= x y 0))]
            [x y]))

(def r [0 1 2 3])

(defn make-paths [x y]
  (for [[dx dy] dirs
        :let [p
              (vec (for [r r
                         :let [x' (+ x (* dx r))
                               y' (+ y (* dy r))]
                         :when (and (nat-int? x') (nat-int? y'))]
                     [x' y']))]
        :when (= 4 (count p))]
    p))

(defn solve1
  [sample]
  (let [lines (str/split-lines sample)]
    (count
     (for [x (range (count lines))
           :let [line (get lines x)]
           y (range (count line))
           :let [c (get line y)]
           :when (= c \X)
           paths (make-paths x y)
           :let [view (apply str
                             (for [[x y] paths]
                               (get-in lines [x y])))]
           :when (or (= view "XMAS")
                     (= view "SAMX"))]
       view))))

(solve1 sample')

(def input
  (->> "input/4"
       io/resource
       slurp))

(solve1 input)
;; => 2370

(defn make-paths2
  [x y]
  [[[(dec x) (dec y)]
    [x y]
    [(inc x) (inc y)]]
   [[(dec x) (inc y)]
    [x y]
    [(inc x) (dec y)]]])


(defn solve2
  [sample]
  (let [lines (str/split-lines sample)]
    (count
     (for [x (range 1 (dec (count lines)))
           :let [line (get lines x)]
           y (range 1 (dec (count line)))
           :let [c (get line y)]
           :when (= c \A)
           :let [paths (make-paths2 x y)
                 views (for [path paths
                             :let [view
                                   (apply str
                                          (for [[x y] path]
                                            (get-in lines [x y])))]
                             :when (or (= view "MAS") (= view "SAM"))]
                         view)]
           :when (= 2 (count views))]
       views))))

Kathryn Isabelle Lawrence 2024-12-04T12:10:50.793719Z

certainly not the most optimized solution, but worked and was the first day so far that I felt the need to use a loop https://github.com/lawreka/aoc24/blob/master/src/aoc24/4.clj

(let [pairs (for [[y line] (map-indexed vector (str/split-lines input))
                  [x ch]   (map-indexed vector line)]
              [[x y] ch])

      xy->ch  (into {} pairs)
      ch->xys (reduce
                (fn [m [xy ch]]
                  (update m ch (fnil conj []) xy))
                {} pairs)

      L  (fn [[x y]] [(dec x) y])
      R  (fn [[x y]] [(inc x) y])
      U  (fn [[x y]] [x (dec y)])
      D  (fn [[x y]] [x (inc y)])
      LU (fn [[x y]] [(dec x) (dec y)])
      RU (fn [[x y]] [(inc x) (dec y)])
      LD (fn [[x y]] [(dec x) (inc y)])
      RD (fn [[x y]] [(inc x) (inc y)])

      score1 (fn [xy]
               (->> [L R U D LU RU LD RD]
                 (filter #(->> xy (iterate %) (take 4) (map xy->ch) (= [\X \M \A \S])))
                 count))

      score2 (fn [xy]
               (->> [(juxt LU identity RD)
                     (juxt RU identity LD)]
                 (map #(->> xy % (map xy->ch) set (= #{\M \A \S})))
                 (every? true?)))]

  (time
    [(->> \X ch->xys (map score1) (reduce +))
     (->> \A ch->xys (filter score2) count)]))

"Elapsed time: 108.984125 msecs"

https://github.com/FelipeCortez/advent-of-code/blob/master/2024/04.clj lots of little helpers (just like santa)

(require '[clojure.string :as str])
(def grid (str/split-lines (slurp "2024/04.in")))
(def size (count grid))
(defn in-bounds? [[i j]] (and (<= 0 i (dec size)) (<= 0 j (dec size))))
(defn bounded-iterate [f x] (into [] (take-while in-bounds? (iterate f x))))
(def e  (partial bounded-iterate (fn [[i j]] [i (inc j)])))
(def s  (partial bounded-iterate (fn [[i j]] [(inc i) j])))
(def se (partial bounded-iterate (fn [[i j]] [(inc i) (inc j)])))
(def sw (partial bounded-iterate (fn [[i j]] [(inc i) (dec j)])))
(def t-edge (e [0 0]))
(def l-edge (s [0 0]))
(def r-edge (s [0 (dec size)]))
(def tl-edges (concat t-edge l-edge))
(def tr-edges (concat t-edge r-edge))
(def lines
  (let [to (into #{} cat [(mapv se tl-edges)
                          (mapv sw tr-edges)
                          (mapv e l-edge)
                          (mapv s t-edge)])
        fro (mapv rseq to)]
    (into #{} cat [to fro])))

(defn char-at [pos] (get-in grid pos))
(def line->word #(apply str (map char-at %)))
(def count-xmas #(count (re-seq #"XMAS" %)))
(reduce + (map (comp count-xmas line->word) lines))

(count
 (for [i (range 1 (dec size))
       j (range 1 (dec size))
       :when (and (= (char-at [i j]) \A)
                  (= #{#{\M \S}}
                     (set [(set [(char-at [(inc i) (dec j)])
                                 (char-at [(dec i) (inc j)])])
                           (set [(char-at [(dec i) (dec j)])
                                 (char-at [(inc i) (inc j)])])])))]
   [i j]))

๐ŸŽ… 1

(defn part-2 [s]
  (let [grid (->> s
                  str/split-lines
                  (map (comp vec seq))
                  vec)
        rows (count grid)
        cols (count (first grid))
        A? (fn [r c] (= \A (get-in grid [r c])))
        ks->char (fn [ks] (get-in grid ks))
        mas? (fn [v] (or (= "MAS" v)
                         (= "MAS" (str/reverse v))))
        path->str (fn [v] (map (fn [p] (str/join (map ks->char p))) v))
        mas-path (fn [sr sc]
                   [[[(- sr 1) (- sc 1)] [sr sc] [(+ sr 1) (+ sc 1)]]
                    [[(+ sr 1) (- sc 1)] [sr sc] [(- sr 1) (+ sc 1)]]])
        indexes (for [r (range 0 rows) c (range 0 cols)] [r c])]
    (->> indexes
         (filter #(apply A? %))
         (map #(apply mas-path %))
         (map path->str)
         (filter (fn [[v1 v2]] (and (mas? v1) (mas? v2))))
         count)))

messily used matrix ops, including rotating matrix by 45deg. code too complex so went with regexing the 3x3 boxes (I kept the rot-45 tho). https://github.com/tschady/advent-of-code/blob/main/src/aoc/2024/d04.clj

(ns aoc.2024.d04
  (:require
   [aoc.file-util :as f]
   [aoc.matrix :as mu :refer [flip-x get-rect]]
   [clojure.core.matrix :as mat]))

(def input (f/read-lines "2024/d04.txt"))

(defn make-matrix [strs] (mapv vec strs))

(defn diagonals [m]
  (let [size (count (first m))]
    (map (partial mat/diagonal m) (range (* -1 (dec size)) size))))

(defn rotations [m]
  (concat m (mat/transpose m) (diagonals m) (diagonals (flip-x m))))

(defn part-1 [input]
  (->> (make-matrix input)
       rotations
       (mapcat #(re-seq #"(?=(XMAS|SAMX))" (apply str %)))
       count))

(defn box-3 [m]
  (let [size (count m)]
    (for [y (range (- size 2))
          x (range (- size 2))]
      (apply str (flatten (get-rect m [x y] 3 3))))))

(defn part-2 [input]
  (->> (make-matrix input)
       box-3
       (keep #(re-find #"(M.M.A.S.S|M.S.A.M.S|S.M.A.S.M|S.S.A.M.M)" %))
       count))

๐Ÿ‘ 1

#yamlscript part1 (works for any word)

!yamlscript/v0
word =: 'XMAS'
defn main(data): !:say
  lines =: data:slurp:lines
  H W =: -[ lines.#, lines.0.# ]
  text =: lines:join
  sum:
    for x W:range, y H:range: !:sum
      for X (-1 .. 1) Y (-1 .. 1):
        loop x x, y y, i 0:
          cond:
            i == word.#: 1
            (-1 < x < W).! || (-1 < y < H).!: 0
            word.$i == text.get(x + (y * H)):
              recur: (X + x) (Y + y) i.++
(you can ys -c part1.ys to see it as Clojure)

no, thanks :D

๐Ÿ˜• 1

#yamlscript part2 (also works for any word)

yamlscript/v0
word =: 'MAS'
defn main(data): !:say
  lines =: data:slurp:lines
  H W L =: -[lines.#, lines.0.#, word.#.--]
  text =: lines:join
  sum:
    each x W:range, y H:range:
      defn match(word S X Y):
        loop i 0, x x, y (y + S):
          cond:
            not((-1 < x < W) && (-1 < y < H)): 0
            word.$i != text.nth(x + (y * W)): 0
            i == L: 1
            else: recur(i.++, (x + X), (y + Y))
      word.match(0 1 1).? || word:reverse.match(0 1 1).? &&:
        word.match(L 1 -1).? || word:reverse.match(L 1 -1).?

(ns stuartstein777.2024.day4
  (:require [clojure.string :as str]))

(defn parse-input []
  (as-> "puzzle-inputs/2024/day4" o
    (slurp o)
    (str/split-lines o)
    (mapv #(mapv identity %) o)))

(defn xmas-horizontal [grid x y xy limit]
  (if (= x limit)
    0
    (let [cells [xy
                 (get-in grid [y (+ 1 x)])
                 (get-in grid [y (+ 2 x)])
                 (get-in grid [y (+ 3 x)])]]
      (if (or (= cells [\X \M \A \S])
              (= cells [\S \A \M \X]))
        1 0))))

(defn xmas-vertical [grid x y xy limit]
  (if (= y limit)
    0
    (let [cells [xy
                 (get-in grid [(+ 1 y) x])
                 (get-in grid [(+ 2 y) x])
                 (get-in grid [(+ 3 y) x])]]
      (if (or (= cells [\X \M \A \S])
              (= cells [\S \A \M \X]))
        1 0))))

(defn xmas-diagonal [grid x y xy limit]
  (if (= y limit)
    0
    (let [diag1 [xy
                 (get-in grid [(+ 1 y) (+ 1 x)])
                 (get-in grid [(+ 2 y) (+ 2 x)])
                 (get-in grid [(+ 3 y) (+ 3 x)])]
          diag2 [xy
                 (get-in grid [(+ 1 y) (- x 1)])
                 (get-in grid [(+ 2 y) (- x 2)])
                 (get-in grid [(+ 3 y) (- x 3)])]]
      (+ (if (or (= diag1 [\X \M \A \S])
                 (= diag1 [\S \A \M \X]))
           1 0)
         (if (or (= diag2 [\X \M \A \S])
                 (= diag2 [\S \A \M \X]))
           1 0)))))

(defn xmas? [grid x y limit]
  (let [xy (get-in grid [y x])]
    (if (or (= xy \S)
            (= xy \X))
      (+ (xmas-horizontal grid x y xy limit)
         (xmas-vertical grid x y xy limit)
         (xmas-diagonal grid x y xy limit))
      0)))

(defn part-1 [grid grid-size]
  (let [limit (- grid-size 3)]
    (loop [x 0
           y 0
           total 0]
      (cond
        (and (= grid-size y) (= grid-size x)) total
        (= grid-size x) (recur 0 (inc y) total)
        :else (recur (inc x) y (+ total (xmas? grid x y limit)))))))

(defn x-mas [grid y x]
  (if (= \A (get-in grid [y x]))
    (let [corners [(get-in grid [(- y 1) (- x 1)])
                   (get-in grid [(- y 1) (+ x 1)])
                   (get-in grid [(+ y 1) (- x 1)])
                   (get-in grid [(+ y 1) (+ x 1)])]]
      (if (or (= corners [\M \M \S \S])
              (= corners [\S \S \M \M])
              (= corners [\S \M \S \M])
              (= corners [\M \S \M \S]))
        1
        0))
    0))

(defn part-2 [grid grid-size] 
  (loop [x 0
         y 0
         total 0]
    (cond
      (and (= grid-size y) (= grid-size x)) total
      (= grid-size x) (recur 0 (+ 1 y) total)
      :else (recur (+ 1 x) y (+ total (x-mas grid x y))))))

(time
 (let [grid (parse-input)
       grid-size (count grid)]
   (part-1 grid grid-size)
   (part-2 grid grid-size)))

;; part 1 - 2514
;; part 2 - 1888

Anyone find a solution that runs in single digit ms? Mine is sitting at roughly 65ms in bb for both parts and wondering if theres any low hanging fruit to pick for perf

how can you time stuff in clojure?

I've just been wrapping the call in time

ah, i see, it outputs the time into the repl. Single digit ms would be impressive. I'm at like 88 for both parts.

hmmm. Only taking into account cells that are an X or S to decide if I should check reduces the average time for 10 runs to around 45ms for both parts. A bit under 45 if I only parse the input once and check the size of the grid once.

; "Elapsed time: 42.646888 msecs"
; "Elapsed time: 46.874505 msecs"
; "Elapsed time: 40.686589 msecs"
; "Elapsed time: 41.899216 msecs"
; "Elapsed time: 40.945747 msecs"
; "Elapsed time: 45.838766 msecs"
; "Elapsed time: 45.894449 msecs"
; "Elapsed time: 45.16648 msecs"
; "Elapsed time: 39.825963 msecs"
; "Elapsed time: 40.118384 msecs"
; "Elapsed time: 39.345266 msecs"

on clj, 10ms for the first part, 30ms for the second (it's very unoptimized lol)

My 'naive' solution for part 2 runs at around 12ms. I don't know how idiomatic it is or not, I'm mostly using this as an opportunity to learn clojure and the core library.

(def matrix (->> (slurp "input/day04.txt")
                 clojure.string/split-lines (map vec) (into [])))

(reduce
 +
 (for [row (range (count matrix))
       col (range (count (matrix 0)))]
   (reduce
    +
    (when (= \X (-> matrix (get row) (get col)))
      (for [ro [-1 0 1] co [-1 0 1] :when (not= 0 ro co)]
        (if (and
             (= \M (-> matrix (get (+ (* 1 ro) row)) (get (+ (* 1 co) col))))
             (= \A (-> matrix (get (+ (* 2 ro) row)) (get (+ (* 2 co) col))))
             (= \S (-> matrix (get (+ (* 3 ro) row)) (get (+ (* 3 co) col)))))
          1 0)))))) ;; => 2496

(def mas #{[\M \M \S \S] [\S \M \M \S] [\S \S \M \M] [\M \S \S \M]})

(reduce
 +
 (for [row (range 1 (dec (count matrix)))
       col (range 1 (dec (count (matrix 0))))
       :when (= \A (-> matrix (get row) (get col)))]
   (if (mas [(-> matrix (get (dec row)) (get (dec col)))
             (-> matrix (get (dec row)) (get (inc col)))
             (-> matrix (get (inc row)) (get (inc col)))
             (-> matrix (get (inc row)) (get (dec col)))])
     1 0))) ;; => 1967

๐Ÿ™Œ 2

Oh using chars, rather than strings. So my grid looks like [[\X \M \A \S... ] [][][]] rather than [["X" "M" "A" "S"... ] [][][]] brings the average down to under 40 now

; "Elapsed time: 35.006057 msecs"
; "Elapsed time: 39.714877 msecs"
; "Elapsed time: 48.287354 msecs"
; "Elapsed time: 35.584586 msecs"
; "Elapsed time: 34.41631 msecs"
; "Elapsed time: 38.806689 msecs"
; "Elapsed time: 37.921028 msecs"
; "Elapsed time: 34.800105 msecs"
; "Elapsed time: 34.823731 msecs"
Surprised that makes such a difference.

This saves 4ms for part 2... ๐Ÿ˜„

(if (#{-990226162 1589543312 -411337452 1188667126} (hash xs)) 1 0)

a little cursed

Wait, I'm checking left to right, and top to bottom. So I can stop left to right once I'm 3 cells from end, same I can ignore the last 3 rows...

[00 01 02 03 04 05]
[06 07 08 09 10 11]
[12 13 14 15 16 17]
[18 19 20 21 22 23]
[24 25 26 27 28 29]
So my first check is [00 01 02 03] and see if that equals [X M A S] or [S A M X] This means left to right I can stop at 02 in this row. For the verticals I check [00 06 12 18], [01 07 13 19] again, for [X M A S] or [S A M X]. This means vertical can stop checking at the row in this example [06 07 08 09 10 11] ... Savings negligble... sigh
; "Elapsed time: 40.264425 msecs"
; "Elapsed time: 56.728862 msecs"
; "Elapsed time: 40.182205 msecs"
; "Elapsed time: 36.505159 msecs"
; "Elapsed time: 49.247117 msecs"
; "Elapsed time: 30.770957 msecs"
; "Elapsed time: 32.070196 msecs"
; "Elapsed time: 30.449591 msecs"
; "Elapsed time: 31.701095 msecs"
; "Elapsed time: 30.870725 msecs"
; "Elapsed time: 38.823972 msecs"
; "Elapsed time: 32.12425 msecs"
Can ignore diagonals for the last 3 rows as well...
; "Elapsed time: 38.477915 msecs"
; "Elapsed time: 34.065827 msecs"
; "Elapsed time: 31.007512 msecs"
; "Elapsed time: 43.321681 msecs"
; "Elapsed time: 30.676098 msecs"
; "Elapsed time: 29.649985 msecs"
; "Elapsed time: 29.772248 msecs"
; "Elapsed time: 29.148957 msecs"
; "Elapsed time: 30.826336 msecs"
; "Elapsed time: 30.584377 msecs"
; "Elapsed time: 33.056277 msecs"
; "Elapsed time: 31.294989 msecs"
Again not much difference really. but at least some times under 30ms now.

least Iโ€™ve got this helper lying around for next time:

(rot-45 example \.)

[[\. \. \. \. \. \. \. \. \. \M \. \. \. \. \. \. \. \. \.]
 [\. \. \. \. \. \. \. \. \M \. \M \. \. \. \. \. \. \. \.]
 [\. \. \. \. \. \. \. \A \. \S \. \M \. \. \. \. \. \. \.]
 [\. \. \. \. \. \. \M \. \M \. \A \. \S \. \. \. \. \. \.]
 [\. \. \. \. \. \X \. \S \. \X \. \M \. \X \. \. \. \. \.]
 [\. \. \. \. \X \. \M \. \A \. \S \. \X \. \X \. \. \. \.]
 [\. \. \. \S \. \X \. \A \. \M \. \X \. \M \. \M \. \. \.]
 [\. \. \S \. \M \. \A \. \S \. \A \. \M \. \S \. \A \. \.]
 [\. \M \. \A \. \S \. \M \. \A \. \S \. \A \. \M \. \S \.]
 [\M \. \A \. \X \. \M \. \M \. \M \. \M \. \A \. \S \. \M]
 [\. \X \. \M \. \A \. \S \. \X \. \X \. \S \. \M \. \A \.]
 [\. \. \M \. \M \. \M \. \A \. \X \. \A \. \M \. \M \. \.]
 [\. \. \. \X \. \M \. \A \. \S \. \A \. \M \. \X \. \. \.]
 [\. \. \. \. \A \. \X \. \S \. \X \. \M \. \M \. \. \. \.]
 [\. \. \. \. \. \X \. \M \. \A \. \S \. \A \. \. \. \. \.]
 [\. \. \. \. \. \. \M \. \M \. \A \. \S \. \. \. \. \. \.]
 [\. \. \. \. \. \. \. \A \. \M \. \A \. \. \. \. \. \. \.]
 [\. \. \. \. \. \. \. \. \S \. \M \. \. \. \. \. \. \. \.]
 [\. \. \. \. \. \. \. \. \. \X \. \. \. \. \. \. \. \. \.]]

๐Ÿ˜ 1

Anyone find a solution that runs in single digit ms? Mine is sitting at roughly 65ms in bb for both parts and wondering if theres any low hanging fruit to pick for perf
My part2 is running at 71ms (SCI based runtime), but yamlscript has super clean idioms that generate clojure that is slower at runtime. But it also has slightly less clean ways to write code that generate faster clojure. (You can make it generate about any clojure you want, if you need to, but the clean idioms are generally fast enough.) I'll take a few minutes to see how much faster I can make it by tweaking a few things...

https://github.com/MMSantana/advent/blob/main/src/clj/days/day_4.clj

(ns clj.days.day-4
  (:require [clojure.string :as string]))

(def input (slurp "resources/inputs/day_4.txt"))

(def test-input "MMMSXXMASM
MSAMXMSMSA
AMXSXMAAMM
MSAMASMSMX
XMASAMXAMM
XXAMMXXAMA
SMSMSASXSS
SAXAMASAAA
MAMMMXMMMM
MXMXAXMASX")

(defn transpose [coll]
  (apply (partial map str) coll))

(defn nil-shift
  [coll l r]
  (into (into (vec (repeat l nil)) coll) (repeat r nil)))

(defn shift-vectors [coll]
  (first
    (reduce
      (fn [[acc l r] nv]
        [(conj acc (nil-shift nv l r)) (inc l) (dec r)])
      [[] 0 (dec (count coll))]
      coll)))

(defn diagonals [coll]
  (apply (partial map str) (shift-vectors (mapv (partial mapv char) coll))))

(defn p1 [in]
  (count
    (filter
      not-empty
      (flatten
        (keep
          #(re-seq #"(?=(XMAS|SAMX))" %)
          (flatten ((juxt identity transpose diagonals (comp diagonals (partial map reverse))) (string/split-lines in))))))))

(defn x-mas?
  [[[a _ b] [_ c _] [d _ e]]]
  (and
    (= c \A)
    (or
      (and (= a b \M) (= d e \S))
      (and (= a d \M) (= b e \S))
      (and (= b e \M) (= a d \S))
      (and (= d e \M) (= a b \S)))))

(defn p2
  [in]
  (->> in
     string/split-lines
     (map (partial partition 3 1))
     (partition 3 1)
     (map (partial apply (partial map (partial conj []))))
     (mapcat identity)
     (filter x-mas?)
     count))

(time (p1 input))
"Elapsed time: 23.655833 msecs"
"Elapsed time: 27.278833 msecs"
"Elapsed time: 18.465667 msecs"
"Elapsed time: 21.610667 msecs"
"Elapsed time: 27.760959 msecs"

(time (p2 input))
"Elapsed time: 21.664833 msecs"
"Elapsed time: 19.043416 msecs"
"Elapsed time: 16.950041 msecs"
"Elapsed time: 16.06425 msecs"
"Elapsed time: 15.401875 msecs"

๐Ÿ™Œ 2

> My part2 is running at 71ms (SCI based runtime), but yamlscript... > I'll take a few minutes to see how much faster I can make it by tweaking a few things... I was able to get from 71ms to 43ms with some small tweaks. ๐Ÿคท

I did a complex index manipulation to use regex and I'm regretting it for part 2 x)

I use regex and I'm regretting it

๐Ÿ˜† 8

Old wisdom says that when you use regex to solve a problem, you've got yourself two problems. An addendum to this is that when you use context-free grammars to solve a problem, you've got yourself ot yourself ot yourself ot yourself ot yourself

๐Ÿ˜† 2
1

Going live at the top of the hour!

๐Ÿ‘ 2