Fork me on GitHub
#clojure
<
2024-02-23
>
Ingy döt Net03:02:16

Wondering a good way to group sets of form (x form)+ in a sequence where x is a particular symbol. Let's say x is '$. I want to turn this:

'(a b c $ d e $ f $ g $ h i $) 
into:
'(a b '(c $ d) '(e $ f $ g $ h) i $)

hiredman03:02:10

Shunting yard algorithm

Ingy döt Net03:02:44

I've written that a couple times.

Ingy döt Net03:02:17

Was looking for something more quick and dirty for this specific case

Ingy döt Net03:02:07

I only really care about that particular symbol at the moment

Ingy döt Net03:02:32

I'm not sure any sequence of forms would convert nicely to rpn

Ingy döt Net03:02:33

I'm gonna try using a couple take-while s

Ingy döt Net05:02:44

This is a fun start:

(defn regroup [xs]
  (loop [xs xs
         ys []
         zs []]
    (if (< (count xs) 3)
      (if (empty? zs)
        (concat ys xs)
        (concat ys [zs] xs))
      (let [[x y] xs]
        (if (empty? zs)
          (if (= '$ y)
            (recur (drop 1 xs) ys [x])
            (recur (drop 1 xs) (conj ys x) []))
          (if (= '$ x)
            (recur (drop 2 xs) ys (conj zs x y))
            (recur xs (conj ys zs) [])))))))
(regroup '(a b $ c $ d e $ f g))
;=> (a [b $ c $ d] [e $ f] g)
but doesn't work for all cases yet, and I'm at eod...

Ingy döt Net05:02:58

If there are any takers I'll add that the input sequence won't have any nil values.

Ingy döt Net05:02:23

I just realized that and likely offers some better solutions.

Jonas Östlund09:02:06

Would clojure.spec (or Malli?) be an option?

(require '[clojure.spec.alpha :as spec])
(spec/def ::data (->> (spec/cat :dollar #{'$}
                                :value any?)
                      spec/*
                      (spec/cat :first any? :rest)
                      spec/*))

(->> '(a b c $ d e $ f $ g $ h i $)
     (spec/conform ::data)
     (mapcat (fn [{:keys [first rest]}]
               (if rest
                 [(into [first] (mapcat (juxt :dollar :value)) rest)]
                 [first]))))
;; => (a b [c $ d] [e $ f $ g $ h] i $)

🎉 1
tomd13:02:52

Slightly shorter, and less nested version of yours @U05H8N9V0HZ. It works on your example input, not sure about others:

(defn- conj-seq [coll x & xs] (apply conj (cond-> coll (seq x) (conj x)) xs))

(defn regroup [sep coll]
  (loop [[a b c :as xs] coll, pt [], acc []]
    (cond (empty? xs) (conj-seq acc pt)
          (and c (= sep b)) (recur (drop 3 xs) (into [] (take 3) xs) (conj-seq acc pt))
          (and b (= sep a)) (recur (drop 2 xs) (into pt (take 2) xs) acc)
          :else (recur (rest xs) [] (conj-seq acc pt a)))))

(regroup '$ '(a b c $ d e $ f $ g $ h i $))
;; => [a b [c $ d] [e $ f $ g $ h] i $]

Ingy döt Net13:02:50

@U03JZS99P27 The spec solution looks amazing. Would be a rabbithole for me to understand it, but a rabbithole worth diving down when I have time 🙂 How costly of an import is it? I need to GraalVM compile it and it feels like something that would probably bloat up my artifact size which is a concern.

Ingy döt Net13:02:17

@UE1N3HAJH thanks for the effort. I was about to fix mine but I'll test yours instead and fix if needed. Will post the final here when done.

👍 1
Jonas Östlund13:02:40

@U05H8N9V0HZ I don't know how costly the import would be. But I would also take a look at Malli: https://github.com/metosin/malli?tab=readme-ov-file#sequence-schemas It has similar syntax for expressing regular grammars using :altn and :* but I don't know Malli well enough. Malli is actively maintained so it may be a good alternative.

Ingy döt Net13:02:33

Added your malli link to my must-learn list! 🙂

👍 1
Ingy döt Net14:02:45

@UE1N3HAJH This works as expected for the test cases shown:

(defn- conj-seq [coll x & xs] (apply conj (cond-> coll (seq x) (conj x)) xs))
(def sep '$)

(defn regroup [coll]
  (loop [[a b c :as xs] coll, pt [], acc []]
    (cond (empty? xs) (conj-seq acc pt)
          (and c (= sep b) (not= a sep) (not= c sep))
          (recur (drop 3 xs) (into [] (take 3) xs) (conj-seq acc pt))

          (and b (= sep a) (not (empty? pt)))
          (recur (drop 2 xs) (into pt (take 2) xs) acc)

          :else
          (recur (rest xs) [] (conj-seq acc pt a)))))

(regroup '())
(regroup '(a))
(regroup '($))
(regroup '($ $))
(regroup '(a b))
(regroup '(a b $))
(regroup '($ b))
(regroup '(a $ $ b $))
(regroup '(a b c))
(regroup '(a b $ c $ d e $))
(regroup '(a b $ c $ d e $ f g))
(regroup '(a b $ c $ d e $ $ f g))
(regroup '($ a b $ c $ d e $ $ f $))
(regroup '($ $ a b $ c $))

Ingy döt Net14:02:37

Just added the (not= a sep) (not= c sep) and (not (empty? pt)) conditions

tomd14:02:52

Nice. Looks much better with the cond rather than nested ifs to me. Have to admit though the spec-based approach does seem beautifully declarative if you can afford the deps.

Ingy döt Net14:02:38

Agreed on both 🙂

Ingy döt Net14:02:44

@U03JZS99P27 would your solution work same on those cases?

Jonas Östlund14:02:32

@U05H8N9V0HZ I am not totally sure about that because there could be ambiguities that Malli handle differently compared to Spec. So you have to try and see what works 🙂

1
Ingy döt Net14:02:39

@UE1N3HAJH Forgot to say that yours handled more cases than mine before I touched it. Good job!

nice 1
Ingy döt Net15:02:15

@UE1N3HAJH Just wondering. Why is the symbol pt?

tomd15:02:01

Short for partition. In clojure.core functions that take runs, they often call it part - sorry being a bit golfy

Ingy döt Net15:02:16

TIL. thx

👍 1
J10:02:09

Hi! It’s possible to tag a deps with scope: provided when building a jar?

futurile11:02:19

Looking for public input from the Clojure community: https://clojureverse.org/t/should-linux-distributions-ship-clojure-byte-compiled-aot-or-not/10595 The Linux distribution I package for is not sure if we should be byte-compiling all Clojure code, just apps and why byte compiling is different in Clojure vs Java

👍 1
Noah Bogart15:02:14

thanks for posting here, i'm hopeful that the Guix maintainers will be able to see reason on this

futurile07:02:32

Agreed @U0HG4EHMH - that's what I thought the position would be: byte-compile an 'app', libs for developers are source distributed. The problem is that while it's obvious to those doing Clojure - I couldn't find a specific statement saying "Distributions and general package managers should not byte-compile libraries they distribute" which makes it's difficult to argue as there's no clear policy statement.

futurile07:02:08

I appreciate thheller, Sean and others taking time on their Friday evening to try and shine some light :-)

phill12:02:41

@UV1JWR18U you might spark an improvement to the documentation by taking the question to http://ask.clojure.org

seancorfield01:02:45

> I couldn't find a specific statement saying "Distributions and general package managers should not byte-compile libraries they distribute" Probably because it wouldn't occur to Clojurians that anyone would even contemplate doing this? (since we all use Maven Central and Clojars to get get our libraries)

phill12:02:38

and (which distro managers might not know) Clojure libraries on Maven Central and Clojars are not compiled

mrnhrd21:02:19

I don't really know what I'm talking about, but since it's guix and those people ought to know their lisp: think of compiled clojure code more like Common Lisp .fasl files (which have little compatibility guarantees) instead of like Java .class files. Just generally the loading/compiling story is a lot more like CL than like Java. Libraries are distributed as source and dynamically compiled by loading them into a language environment. AOT (and uberjars btw) is for final application distribution (rough parralels to sbcl images), not for library distribution.

mrnhrd21:02:34

Clojure uses maven/java package formats (jars and so on) to play nice with the java world and piggyback on maven infrastructure, but unlike java, those jars do not contain compiled code, they contain source files. It's like quicklisp in that regard.