adventofcode 2024-12-19

(I was cleaning my keyboard and slack opened) oops

No stream today. I'm done for the season. Ya'll have fun!

🙌 5
🙌🏻 1

unrelated, does someone have a lib I could use to fit a curve to some points?

Like I have 4 points and I want a quadratic curve for it

Probably better to ask in #clojure.

memoized recursive function. this always feels kind of awkward in clojure and I do the y combinator thing - interested to see how other people did this. https://github.com/tildedave/advent-of-code/blob/main/src/advent2024/day19.clj funny enough I programmed it to be memoized for part 1 but forgot to actually memoize it 😅

It’s not complicated at all, just use def

Or rebind the variable

This y-combinator thing can still blow up if recursion is too deep, right?

yeah it's pretty janky. I know memoize can work with 2 args but I don't like re-checking equality on the contxt variables.

maybe perf wise it doesn't matter for this example but I believe I've run into slowness on the "extra args checking" before

should be fast

yeah I'll futz a bit with it

because the first thing it checks when comparing maps is if they are the literal same object. And if you don’t modify anything they should be

Oh it does? That's clever. I'm actually less concerned about partial memoization if it memoizes on a ref.

it would be immensely stupid not to do that check

it’s tons cheaper than running actual equality on contents, it’s basically one of the benefits of immutable objects

so you should first check if it’s the same object, then if both are of types that are even possibly equal, then, if the object has cheap count function, you compare counts, and only then you actually look at the elements

That makes a lot of sense. I always forget that objects are passed by ref in Clojure if not modified, since functionally, we can always think that copies are passed around. Thank you for your lights 🙂

It works the same way in java

Or C#, Kotlin, Scala, etc

I've been fortunate enough not to ever work seriously with Java. I have very superficial understanding of how it works.

Most languages have pass-by-ref as default parameters

Yeah, I know. But the very functional approach of Clojure made me forget that.

It's not obvious to me why I didn't need to memoize in part 1. Is it just laziness kicking in?

(defn doable? [design]
  (if (empty? design) 
   :valid
   (seq (keep 
          (fn [p]
             (when (str/starts-with? design p)
               (doable? (subs design (count p)))))
          patterns))))

(->> designs (keep doable?) count) 

I took a regular expression approach. I compiled the patterns into a tree and then matched the designs against them recursively. Used memoization for part 2. The benefit of the reg-exp approach is that the algorithm doesn’t have to backtrack as far to try the next design. Memoization is more effective as well.

@jvuillermet I think you’d want to call first instead of seq on your (keep expression to benefit from laziness

Just glad that it's solved.

👍 1

I felt really smart today. very welcome after two frustrating days at work debugging a 80 line SQL query and pulling out my hair

wow, @erdos's is even cleaner. incredible

short and easy. I tried to nest my memo and it was a bit uglier than I hoped.. https://gitlab.com/maximoburrito/advent2024/-/blob/main/src/day19/main.clj

For some reason I tried to do part2 without memoize, iterating just on the possible patterns. Lost like 20minutes because of sheer stubbornness -_- EDIT: DRY-ing

Well this sucks. My alarm didn’t go off.

2

Solved it in 13 minutes, now I am at 4000-6000th place

Nested memo? what?

I wish memoize took some optional arguments that let you specify "memoize on only the last argument". Would make the space required way better. I know I could do it with inline declaration but that's just ugly.

clojure core memoize will let you do that

Oh this is pretty cool, but way overkill for my needs. Bookmarkign it though. Thank you.

But my library is not focused on speed, because it has advanced features and it assumes each invocation takes 1 ms at least.

E.g. it uses extra locking to purge entries from multiple caches when you are invalidating by tag, so that slows down the simple case quite a bit

As I needed to introduce additional locks

When speed is of the essence, I wrote my own memoize:

(defn memoize*
  "Faster memoize not thread safe."
  [f]
  (let [mem (HashMap.)
        guard (Object.)]
    (fn [& args]
      (let [v (.getOrDefault mem args guard)]
        (if (= v guard)
          (let [ret (apply f args)]
            (.put mem args ret) ret)
          v)))))

Cute. I'm stealing it 😛

Here you go, a version where you can specify a key transformation:

(defn memoize*
  "Faster memoize not thread safe."
  ([f] (memoize* f identity))
  ([f key-fn]
   (let [mem (HashMap.)
         guard (Object.)]
     (fn [& args]
       (let [v (.getOrDefault mem (key-fn args) guard)]
         (if (= v guard)
           (let [ret (apply f args)]
             (.put mem args ret) ret)
           v))))))

sorry a bug here

That's basically what I did, yeah. Yours is more idiomatic though (I just did a memoize-n which stores on take n args).

(defn memoize*
  "Faster memoize not thread safe."
  ([f] (memoize* identity f))
  ([key-fn f]
   (let [mem (HashMap.)
         guard (Object.)]
     (fn [& args]
       (let [k (key-fn args)
             v (.getOrDefault mem k guard)]
         (if (= v guard)
           (let [ret (apply f args)]
             (.put mem k ret) ret)
           v))))))

1