Question

I have a data structure of the form ["a" "b" " c" "d" " e" " f" "g"]

I'd like to reduce that to ["a" "b c" "d e f" "g"], that is, strings that begin with a space are joined to the one preceding them.

I've been struggling with it for a couple of hours, and am looking for some inspiration!

Was it helpful?

Solution 2

Hybrid approach where the filter and the initial accumulator handle the first starts with a space case.

(defn join-when-space [vs]
  (mapv (partial apply str) 
    (filter not-empty 
      (reduce (fn [a s] (if (re-find #"^\s" s) 
                          (conj (pop a) (conj (peek a) s)) 
                          (conj a [s]))) 
         [[]] vs))))

OTHER TIPS

This splits the string sequence into blocks, then joins all groups of strings which should be joined with a single string/join call per group, so it avoids the quadratic behaviour of solutions pastings strings together one by one:

(def xs ["a" "b" " c" "d" " e" " f" "g"])

(require '[clojure.string :as string])

(->> xs
     (partition-by #(.startsWith ^String % " "))
     (map vec)
     (partition-all 2)
     (reduce (fn [acc [ps ss]]
               (-> acc
                   (into (pop ps))
                   (conj (string/join "" (cons (peek ps) ss)))))
             []))
;= ["a" "b c" "d e f" "g"]

Note that this assumes that the first string does not start with a space. To do away with this assumption, you could prepend an empty string ((cons "" xs) instead of xs in the above, or (cons "") as the first ->> step) to "catch" them. In that case the result would either start with a string resulting from joining together the sequence-initial strings starting with spaces or an empty space if the sequence does not start with such strings, so you can check for the presence of "" at the first position in the result and possibly filter it out.

The following is one way of doing it using reduce, although there's probably a more elegant way of doing the same thing - there often is in Clojure :)

(defn join-when-space [v]
   (->> (reduce (fn [acc next-value]
                   (if (re-matches #"^ .*" next-value)
                     (concat (butlast acc) [(str (last acc) next-value)])
                     (concat acc [next-value]))) 
                [[] (first v)] (rest v))
         rest
         (into [])))

This function produces a lazy sequence of the sequence strings spliced on initial spaces:

(defn spliced-seq [strings]
  (let [[x & xs] strings]
   (if (empty? xs)
    [x]
    (let [[ys & ns] (split-with #(.startsWith % " ") xs)]
      (cons (apply str x ys) (lazy-seq (f ns)))))))

Then (vec (spliced-seq) ["a" " b" " c" "d" "e"])) produces ["a b c" "d e"].

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top