Search code examples
clojure

How to merge two lists of maps in Clojure


I have data in below format.

'(({:nums {:test number?, :data (1)}})
  {:Other ()} 
  ({:nums {:test number?, :data (2)}})
  ({:nums {:test number?, :data (3 4)}}))

But I would like to get data in below format.

'(({:nums {:test number?, :data (1 2 3 4)}}) 
  {:Other ()})

Tried with merge, union functions, but not worked. Could you please help here as I am beginner to Closure.

Doing tests on below code

(split '(1 2 (3 4 [1 2 3])) {:nums {:test number?}})
=> 
((({:nums {:test number?, :data (1)}}) {:Other ()})
  (({:nums {:test number?, :data (2)}}) {:Other ()})
  (({:nums {:test number?, :data (3 4)}}) {:Other (([1 2 3]))}))

But The target result I am expecting as below

(split '(1 2 (3 4 [1 2 3])) {:nums {:test number?}})
=> 
(({:nums {:test number?, :data (1 2 3 4)}}) 
 {:Other ([1 2 3])})

Code used:

(defn test
  [list1 filter1]
  ;(get-in (first (first (split list1 filter1))) [:nums :test])
  (first (first (split list1 filter1))))

(defn split [list1 filter_map]
  (if (empty? list1)
    ()
    (cons (split_list
            (if (sequential? (first list1))
              (first list1)
              (list (first list1)))
            filter_map)
      (split (rest list1) filter_map))))

(defn split_list [list1 filter_map]
  (let [var_in_filter (remove nil? (in-filter filter_map))
        var_out_filter (remove nil? (out-filter filter_map))]
    ;var_out_filter
    (list (in_filter_recur list1 var_in_filter)
      ;(in_filter_recur list1  var_in_filter)
      (hash-map :Other (remove nil?
                         (out_filter_recur list1 var_out_filter))))))

(defn in_filter_recur [lis in_filt]
  (if (empty? in_filt)
    nil
    (cons (hash-map
            (if ((first in_filt) 1)
              :nums
              ;"{:nums {:test number?}"
              (if ((first in_filt) 'a)
                :syms
                (if ((first in_filt) [1])
                  :vects
                  'others)))

            ;(set (merge [:data] (filter (first in_filt) lis))))
            (hash-map :test
              (if ((first in_filt) 1)
                'number?
                ;"{:nums {:test number?}"
                (if ((first in_filt) 'a)
                  'symbols?
                  (if ((first in_filt) [1])
                    'vector?
                    'others)))
              :data (filter (first in_filt) lis)))
      (in_filter_recur lis (rest in_filt)))))

(defn out_filter_recur [lis out_filt]
  (if (empty? out_filt)
    nil
    (cons ;(hash-map nil
      (if (empty? (filter (first out_filt) lis))
        nil
        (filter (first out_filt) lis))
      (out_filter_recur lis (rest out_filt)))))

(defn out-filter [filt]
  (difference 
    (set 
      (All-filter
        {:nums {:test number?}
         :syms {:test symbol?}
         :vects {:test vector?}
         :other {:test string?}})) (set (in-filter filt))))

(defn in-filter [filt]
  (list (when
          (and (> (count (str (get-in filt [:nums :test]))) 0)
            ((get-in filt [:nums :test]) 1))
          number?)
    (when
      (and (> (count (str (get-in filt [:syms :test]))) 0)
        ((get-in filt [:syms :test]) 'a))
      symbol?)
    (when
      (and (> (count (str (get-in filt [:vects :test]))) 0)
        ((get-in filt [:vects :test]) [1]))
      vector?)))



(defn All-filter [filt]
  (list (when
          (and (> (count (str (get-in filt [:nums :test]))) 0)
            ((get-in filt [:nums :test]) 1))
          number?)
    (when
      (and (> (count (str (get-in filt [:syms :test]))) 0)
        ((get-in filt [:syms :test]) 'a))
      symbol?)
    (when
      (and (> (count (str (get-in filt [:vects :test]))) 0)
        ((get-in filt [:vects :test]) [1]))
      vector?)
    (when
      (and (> (count (str (get-in filt [:other :test]))) 0)
        ((get-in filt [:other :test]) "1"))
      string?)))


(defn difference
  ([s1] s1)
  ([s1 s2]
   (if (< (count s1) (count s2))
     (reduce (fn [result item]
               (if (contains? s2 item)
                 (disj result item)
                 result))
       s1 s1)
     (reduce disj s1 s2)))
  ([s1 s2 & sets]
   (reduce difference s1 (conj sets s2))))

Solution

  • Here's what I came up with, it's kind of a specific data structure, so it's pretty specific code

    The first step is to set up the data so that we can work with merge-with

    (defn get-data [val]
      (as-> val ?
            (first ?)
            (:nums ?)
            (select-keys ? [:data])))
    

    Then the last step is to call merge-with and put it back together

    (def x '(({:nums {:test number?, :data (1)}})
             {:Other ()} 
             ({:nums {:test number?, :data (2)}})
             ({:nums {:test number?, :data (3 4)}}))
    
    (as-> x ? 
          (map get-data ?)
          (apply merge-with concat ?)
          (assoc ? :test 'number?)
          (list ?)
          (list ?)
          (conj ? {:Other '()})
          (reverse ?))
    

    If you were looking for something more generic, you'll have to come at me with a more generic data structure.

    I think what you were looking for is mostly contained in the (apply merge-with concat ?) line which does most of the work.

    This function takes a list of maps and applies the function, in this case concat to the values in those maps.

    See (doc merge-with) in your REPL for more information.

    As a last ditch effort, you may also be interested in the specter clojure library which excels at this sort of deeply nested data transformation.

    Hope this helps!