From 069f24c05110b2d1791a0bc7bd6559459037b9f9 Mon Sep 17 00:00:00 2001 From: Noah Bogart Date: Fri, 27 Mar 2026 09:39:33 -0400 Subject: [PATCH 1/2] Add map-padded --- src/medley/core.cljc | 28 ++++++++++++++++++++++++++++ test/medley/core_test.cljc | 25 +++++++++++++++++++++++++ 2 files changed, 53 insertions(+) diff --git a/src/medley/core.cljc b/src/medley/core.cljc index 256df99..65263db 100644 --- a/src/medley/core.cljc +++ b/src/medley/core.cljc @@ -706,3 +706,31 @@ (if (next ks) (-> (get-in m (butlast ks)) (find (last ks))) (find m (first ks)))) + +(letfn [(first* [s v] (if s (first s) v))] + (defn map-padded + "Similar to `clojure.core/map`, except that it runs until all colls are + exhausted, using `val` as the missing value for each exhausted coll." + {:added "<>"} + #_{:clj-kondo/ignore [:unused-binding]} + ([f val c1] (map f c1)) + ([f val c1 c2] + (lazy-seq + (let [s1 (seq c1) s2 (seq c2)] + (when (or s1 s2) + (cons (f (first* s1 val) (first* s2 val)) + (map-padded f val (rest s1) (rest s2))))))) + ([f val c1 c2 c3] + (lazy-seq + (let [s1 (seq c1) s2 (seq c2) s3 (seq c3)] + (when (or s1 s2 s3) + (cons (f (first* s1 val) (first* s2 val) (first* s3 val)) + (map-padded f val (rest s1) (rest s2) (rest s3))))))) + ([f val c1 c2 c3 & colls] + (let [step (fn step [cs] + (lazy-seq + (let [ss (mapv seq cs)] + (when (some identity ss) + (cons (apply f (mapv #(first* % val) ss)) + (step (mapv rest ss)))))))] + (step (conj colls c3 c2 c1)))))) diff --git a/test/medley/core_test.cljc b/test/medley/core_test.cljc index 2f5b70c..14c5544 100644 --- a/test/medley/core_test.cljc +++ b/test/medley/core_test.cljc @@ -547,3 +547,28 @@ (is (= [:b 2] (m/find-in {:a {:b 2}} [:a :b]))) (is (= [:b {:c 3}] (m/find-in {:a {:b {:c 3}}} [:a :b]))) (is (= [:c 3] (m/find-in {:a {:b {:c 3}}} [:a :b :c])))) + +(deftest test-map-padded + (is (= (map + (range 3) (range 4) (range 5) (range 10)) + [0 4 8] + (take 3 (m/map-padded + 0 (range 3) (range 4) (range 5) (range 10))))) + (is (= [0 4 8 19 28 35 36 37 38 39] + (m/map-padded + 10 (range 3) (range 4) (range 5) (range 10)))) + (is (= () + (m/map-padded + 10 () () ()))) + (testing "laziness" + (let [state (volatile! [])] + (is (= [0 4 8 19 28] + (take 5 (m/map-padded (fn [a b c d] + (vswap! state conj [a b c d]) + (+ a b c d)) + 10 + (range 3) (range 4) (range 5) (range 10))))) + (is (= [[0 0 0 0] [1 1 1 1] [2 2 2 2] [10 3 3 3] [10 10 4 4]] + @state)))) + (testing "handles sequences with nils" + (is (= [[nil 0 0 0] [nil 1 1 1] [nil 2 2 2] + [:missing 3 3 3] + [:missing :missing 4 4] + [:missing :missing :missing 5]] + (take 6 (m/map-padded vector :missing [nil nil nil] (range 4) (range 5) (range 10))))))) From 6b27cb119f4fceeafa6adff40937883a382cdb7c Mon Sep 17 00:00:00 2001 From: Noah Bogart Date: Fri, 27 Mar 2026 17:00:05 -0400 Subject: [PATCH 2/2] Add sequence-padded --- src/medley/core.cljc | 20 ++++++++++++++++++- test/medley/core_test.cljc | 40 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 59 insertions(+), 1 deletion(-) diff --git a/src/medley/core.cljc b/src/medley/core.cljc index 65263db..2415353 100644 --- a/src/medley/core.cljc +++ b/src/medley/core.cljc @@ -733,4 +733,22 @@ (when (some identity ss) (cons (apply f (mapv #(first* % val) ss)) (step (mapv rest ss)))))))] - (step (conj colls c3 c2 c1)))))) + (step (conj colls c3 c2 c1)))))) + +(defn sequence-padded + "Similar to `clojure.core/sequence`, except that it runs until all colls are + exhausted, using `val` as the missing value for each exhausted coll." + #_{:clj-kondo/ignore [:unused-binding]} + ([xform val c1] (sequence xform c1)) + ([xform val c1 & colls] + (let [f (xform (completing conj)) + step (fn step [cs] + (lazy-seq + (let [ss (mapv seq cs)] + (if (some identity ss) + (let [res (apply f nil (mapv #(if % (first %) val) ss))] + (cond (reduced? res) (f (deref res)) + (nil? res) (step (mapv rest ss)) + :else (concat res (lazy-seq (step (mapv rest ss)))))) + (f nil)))))] + (step (conj colls c1))))) diff --git a/test/medley/core_test.cljc b/test/medley/core_test.cljc index 14c5544..dc35d93 100644 --- a/test/medley/core_test.cljc +++ b/test/medley/core_test.cljc @@ -572,3 +572,43 @@ [:missing :missing 4 4] [:missing :missing :missing 5]] (take 6 (m/map-padded vector :missing [nil nil nil] (range 4) (range 5) (range 10))))))) + +(deftest test-sequence-padded + (is (= (map + (range 3) (range 4) (range 5) (range 10)) + [0 4 8] + (take 3 (m/sequence-padded (map +) 0 (range 3) (range 4) (range 5) (range 10))))) + (is (= [0 4 8 19 28 35 36 37 38 39] + (m/sequence-padded (map +) 10 (range 3) (range 4) (range 5) (range 10)))) + (is (= () + (m/sequence-padded (map +) 10 () () ()))) + (testing "laziness" + (let [state (volatile! [])] + (is (= [0 4 8 19 28] + (take 5 (m/sequence-padded + (map (fn [a b c d] + (vswap! state conj [a b c d]) + (+ a b c d))) + 10 + (range 3) (range 4) (range 5) (range 10))))) + (is (= [[0 0 0 0] [1 1 1 1] [2 2 2 2] [10 3 3 3] [10 10 4 4]] + @state)))) + (testing "handles sequences with nils" + (is (= [[nil 0 0 0] [nil 1 1 1] [nil 2 2 2] + [:missing 3 3 3] + [:missing :missing 4 4] + [:missing :missing :missing 5]] + (take 6 (m/sequence-padded (map vector) :missing [nil nil nil] (range 4) (range 5) (range 10)))))) + (testing "slightly more complex xform" + (let [xf + (comp + (map vector) + (drop 5) + (filter (comp even? second)) + (map #(mapv inc %)) + (take 5))] + (is (= [[101 19 507] [101 25 509] [101 31 511] [101 37 513] [101 43 515]] + (m/sequence-padded xf + 100 + (range 5) + (range 0 50 3) + (range 500 1000)))))))