Fork me on GitHub
#clojure
<
2024-02-01
>
celebrimbor37902:02:19

I've been noodling around with "Elements of Programming Interviews" (highly recommended if you enjoy algorithm puzzles) and I'm wondering if there's an idiomatic Clojure/functional way to implement an imperative-style "sliding-window" algorithm? Example: find the smallest subarray that contains all members of a set, e.g. given array ["a", "b", "c", "b", "a", "d", "c", "a", "e", "a", "a", "b", "e"] and set {"b", "c", "e"}, the smallest subarray containing b, c, and e is from indexes 3 - 8. One way to do this is to have two pointers starting at index 0, run the first pointer down the array until it's encountered at least one of each of the set members, then run the second pointer behind it until the subarray they span no longer contains all set members, then repeat until the first pointer reaches the end, keeping track of the shortest distance encountered between the two pointers. I can't think of a way to do this in Clojure that doesn't either do essentially the same thing with a while loop or something, or else generates a list of all possible subarrays and then filters them, which is not nearly as efficient.

hiredman02:02:01

If generating all possible subarrays is lazy then it may be surprisingly efficient. unlikely to beat the loop over indices, but not as bad as you might think.

hiredman02:02:16

But fundamentally there is nothing about looping that is not functional

hiredman02:02:19

In an imperative language you'll use a for or while loop, in a functional one you'll use some kind recursion

celebrimbor37904:02:49

Excellent points -- I work on very-much-not-functional enterprise Java for my day job so maybe I'm overthinking the functional part. I'd thought about a recursive solution but it just seemed like fundamentally the same thing as moving pointers around, but maybe that's ok.

Max04:02:58

I don’t know if this is a good idea or not, but it might be an interesting one: • have a recursive fn or loop that takes a seq and an accumulator • take-while on the first seq until it contains all the set members • Take the seq you found and drop-while all the set members are still in it. I might do this in a separate fn rather than a tight loop. • Add that span to the accumulator and repeat until the seq is empty You’ll want to use split-with rather than take/drop-while so you have access to the rest of the seq after taking/dropping the elements you want, but it’ll be take/drop-while-like in spirit. Performance probably won’t be as good as if you used a loop because of the seq overhead, but maybe it’ll get close if you use transducers?

tomd10:02:38

Using partition with a step-size of 1 is the standard way to get a "sliding window" over a collection in clojure, so something like this would be a fairly typical approach:

(defn shortest-subseq [test-set coll]
  (let [matching (fn [ss] (when (every? (fn [p] (some #(= p %) ss)) test-set) ss))]
    (loop [part-size (count test-set)]
      (when (<= part-size (count coll))
        (or (some matching (partition part-size 1 coll))
            (recur (inc part-size)))))))

(shortest-subseq #{"b" "c" "e"} input)
;; => ("b" "a" "d" "c" "a" "e")

🎯 2
onetom05:02:27

Would any commonly used library break, if I define a print-method for (byte-array)s, printing them as hex strings? (defmethod print-method (class (byte-array 0)) ...) I want to be able to roundtrip some byte-arrays through AWS SSM params as EDN, without custom handling for byte-array values.

onetom05:02:27

something like this:

;;; Experiment for automatically returning SSM secret param values as a byte-array
(def ^java.util.HexFormat hex-formatter (java.util.HexFormat/of))
(defn parse-hex ^bytes [^String hex-str] (.parseHex hex-formatter hex-str))
(defn format-hex ^String [^bytes a-byte-array] (.formatHex hex-formatter a-byte-array))

(defmethod print-method (class (byte-array 0))
  [a-byte-array ^java.io.Writer writer]
  (doto writer
    (->> (print-method (tagged-literal 'hex (format-hex a-byte-array))))))

(defn edn-encode [x]
  (binding [*print-level* 32 *print-length* nil *print-dup* false *print-namespace-maps* false]
    (pr-str x)))

(defn edn-decode [edn-str]
  (edn/read-string {:readers {'hex parse-hex}} edn-str))

(comment
  (-> [1 2 3 10 11 12] byte-array edn-encode edn-decode)
  )

onetom05:02:01

and it should return

=> #hex"0102030a0b0c"

onetom07:02:30

btw, I just heard about java.util.HexFormat a few months ago, which was introduced in Java 17. Here is a quick intro article about it: https://www.baeldung.com/java-hexformat

nice 2
ghadi13:02:12

you can extend print-method to byte arrays in an application, but not in a library

🙏 2
onetom14:02:24

also, is there a shorter way to say (class (byte-array 0))?

ghadi14:02:27

in clojure 1.12 there will be

onetom14:02:19

oh, really? i heard some hints at that in alex miller's recent talk about java interop, but can't remember seeing anything about it in the dev changelog. im using 1.12.0-alpha5 locally.

ghadi14:02:33

that stuff hasn't landed quite yet

p-himik20:02:29

Curious - why byte* instead of making bytes a first-class citizen?

ghadi02:02:33

lots of reasons for that, if you made an actual class named bytes that would create an ambiguity. * is not a legal character for a class name

👍 1