Clojure Compressed Vector
I'm trying to find a Clojure-idiomatic way to "squeeze" a vector:
(shift-nils-left [:a :b :c :a nil :d nil]) ;=> (true [nil nil :a :b :c :a :d]) (shift-nils-left [nil :a]) ;=> (false [nil :a]) (shift-nils-left [:a nil]) ;=> (true [nil :a]) (shift-nils-left [:a :b]) ;=> (false [:a :b]) In other words, I want to move all nil values ββto the left end of the vector without changing the length. A boolean indicates whether any bias has occurred. The "external" structure can be any seq , but the internal result must be a vector.
I suspect that the function will include filter (by nil values) and into to add to the nil vector the same length as the original, but I'm not sure how to reduce the result back to the original length. I know how to "play long", but I suspect that Clojure will be able to do this on one line.
I play with the idea of ββwriting a Bejeweled player as an exercise to learn Clojure.
Thanks.
I would write this as follows:
(ns ... (:require [clojure.contrib.seq-utils :as seq-utils])) (defn compress-vec "Returns a list containing a boolean value indicating whether the vector was changed, and a vector with all the nils in the given vector shifted to the beginning." ([v] (let [shifted (vec (apply concat (seq-utils/separate nil? v)))] (list (not= v shifted) shifted)))) Edit: just like the fact that Thomas beat me up for publication, but I would not use a flattening just in case, if you end up using some kind of separate object to represent jewelry.
A slightly lower level approach. It traverses the seq input only once, and also the non-nil vector once. Two higher-level approaches go through the input sequence two times (for nil? And (complenent nil?) ). not= passes the third time in the worst case without a shift.
(defn compress-vec [v] (let [[shift? nils non-nils] (reduce (fn [[shift? nils non-nils] x] (if (nil? x) [(pos? (count non-nils)) (conj nils nil) non-nils] [shift? nils (conj non-nils x)])) [false [] []] v)] [shift? (into nils non-nils)])) (def v [1 2 nil 4 5 nil 7 8] ) (apply vector (take 8 (concat (filter identity v) (repeat nil)))) This creates a sequence of non nil values ββin the vector using filter , and then adds nils to the end of the sequence. This gives the values ββyou want as a sequence and then converts them to a vector. take 8 ensures that the vector is the correct size.