Search code examples
clojure

How to transform seq into vector without so much verbosity?


I am trying to implement reorder functionality for a vector of maps. For now, I want to understand how to move un-nested items inside the vector, before I tackle nested vectors, etc.

Trying to learn clojure on the way...

Here's what I came up with:

(defn split-at' [idx v]
  [(subvec v 0 idx) (subvec v idx)])

(defn my-reorder [lst start-pos end-pos]
  (let [item-to-move (get lst start-pos)
        spliced (remove-from-vec lst start-pos)
        [left-side right-side] (split-at' end-pos spliced)]
    (vec
      (flatten
        (conj [] (if (empty? left-side) [] left-side) item-to-move (if (empty? right-side) [] right-side)))
    )))

(my-reorder items 7 6)

If I don't do the whole checkups and flattening, I get vectors wrapping various elements which is unwanted. Here's how sample data looks like:

(def items [
         {:id "1" :text "HELLO I am First"}
         {:id "2" :text "RENDER IT!"}
         {:id "3" :children [{:id "4" :text "Im a child"}
                            {:id "5" :text "Im special!"}
                            {:id "6" :children [{:id 7 :text "I am so deep !!!"}]}]}
         {:id "8" :text "GoodBye I am last"}
         {:id "9" :children [{:id "10" :text "I am Number 10!"}]}
         {:id "13" :text "GoodBye I am last"}
          {:id "14" :text "GoodBye I am last"}
         {:id "15" :text "GoodBye I am last"}
         ])

Is there a more concise way to deal with this? Ultimately I want to get back the same vector just with new order.

I tried flattening without doing nested if-checks and resulted in mixes results.


Solution

  • maybe something like this will work:

    (defn upd [from to data]
      (vec (mapcat (fn [i x] (cond (= i from) []                               
                                   (= i to) (if (< from to) 
                                              [x (data from)]
                                              [(data from) x])
                                   :else [x]))
                   (range)
                   data)))
    

    it just passes through indexed collection, remving item at from, prepending it to the part of seq started with to index:

    (upd 2 0 [1 2 3 4 5])
    ;; [3 1 2 4 5]
    
    (upd 0 2 [1 2 3 4 5])
    ;; [2 3 1 4 5]
    

    maybe it's not the most elegant of them all, but at least it is done in one pass, and doesn't involve flattening, which is almost always not the best idea (at least in your case)

    also, you can avoid double pass (mapcat + vec) by using transducer:

    (defn upd [from to data]
      (into [] (mapcat (fn [i] (cond (= i from) []                               
                                     (= i to) (if (< from to) 
                                                [(data i) (data from)]
                                                [(data from) (data i)])
                                     :else [(data i)])))
            (range (count data))))