Clojure将地图的向量转换为向量值的地图

时间:2019-01-25 14:39:47

标签: clojure

有一些与此主题相关的SO帖子,但是我找不到适合我要完成的工作的东西。

我有地图矢量。我将使用另一个相关的SO帖子中的示例:

(def data
   [{:id 1 :first-name "John1" :last-name "Dow1" :age "14"}
    {:id 2 :first-name "John2" :last-name "Dow2" :age "54"}
    {:id 3 :first-name "John3" :last-name "Dow3" :age "34"}
    {:id 4 :first-name "John4" :last-name "Dow4" :age "12"}
    {:id 5 :first-name "John5" :last-name "Dow5" :age "24"}]))

我想将其转换为一个映射,其中每个条目的值都是关联值的向量(保持data的顺序)。

这是我想要的输出:

{:id [1 2 3 4 5]
 :first-name ["John1" "John2" "John3" "John4" "John5"]
 :last-name ["Dow1" "Dow2" "Dow3" "Dow4" "Dow5"]
 :age ["14" "54" "34" "12" "24"]}

在Clojure中,有一种优雅而有效的方法吗?

5 个答案:

答案 0 :(得分:5)

可以提高效率,但这是一个不错的开始:

(def ks (keys (first data)))
(zipmap ks (apply map vector (map (apply juxt ks) data))) ;;=> 

{:id [1 2 3 4 5]
 :first-name ["John1" "John2" "John3" "John4" "John5"]
 :last-name ["Dow1" "Dow2" "Dow3" "Dow4" "Dow5"]
 :age ["14" "54" "34" "12" "24"]}

另一个接近的东西:

(group-by key (into [] cat data))

;;=> 
{:id [[:id 1] [:id 2] [:id 3] [:id 4] [:id 5]],
 :first-name [[:first-name "John1"] [:first-name "John2"] [:first-name "John3"] [:first-name "John4"] [:first-name "John5"]],
 :last-name [[:last-name "Dow1"] [:last-name "Dow2"] [:last-name "Dow3"] [:last-name "Dow4"] [:last-name "Dow5"]],
 :age [[:age "14"] [:age "54"] [:age "34"] [:age "12"] [:age "24"]]}

答案 1 :(得分:2)

好吧,我制定了一个解决方案,然后在发布之前,米歇尔(Michiel)发布了一个更简洁的解决方案,但无论如何我都会继续发布它=)。

(defn map-coll->key-vector-map
  [coll]
  (reduce (fn [new-map key] 
            (assoc new-map key (vec (map key coll))))
          {}
          (keys (first coll))))

答案 2 :(得分:0)

对我来说,这里最清晰的方法是:

(defn ->coll [x]
  (cond-> x (not (coll? x)) vector))

(apply merge-with #(conj (->coll %1) %2) data)

基本上,这里的任务是合并所有映射(merge-with),并通过将(conj)连接到键上的向量来收集同一键上的所有值-确保值是实际上连接到向量(->coll)上。

答案 3 :(得分:0)

如果我们将地图连接成一个成对的单一序列,那么我们将具有图的边列表表示。我们要做的就是将其转换为adjacency-list(这里是向量,而不是列表)表示形式。

(defn collect [maps]
  (reduce
    (fn [acc [k v]] (assoc acc k (conj (get acc k []) v)))
    {}
    (apply concat maps)))

例如

=> (collect data)
{:id [1 2 3 4 5]
 :first-name ["John1" "John2" "John3" "John4" "John5"]
 :last-name ["Dow1" "Dow2" "Dow3" "Dow4" "Dow5"]
 :age ["14" "54" "34" "12" "24"]}

此方法相对于其他一些方法的优势在于,给定序列中的图可以具有不同的形状。

答案 4 :(得分:-1)

编写代码时请考虑读者!打“代码高尔夫”没有奖品。但是,当您迫使其他人解密过于精简的代码时,对其他人来说,代价是巨大的。

我总是试图明确地说明代码在做什么。如果将问题分解为简单的步骤并使用好名字,这是最容易的。特别是,几乎不可能使用juxt或任何其他隐函数来完成此操作。

这是我实施解决方案的方式:

(def data
  [{:id 1 :first-name "John1" :last-name "Dow1" :age "14"}
   {:id 2 :first-name "John2" :last-name "Dow2" :age "54"}
   {:id 3 :first-name "John3" :last-name "Dow3" :age "34"}
   {:id 4 :first-name "John4" :last-name "Dow4" :age "12"}
   {:id 5 :first-name "John5" :last-name "Dow5" :age "24"}])

(def data-keys (keys (first data)))

(defn create-empty-result
  "init result map with an empty vec for each key"
  [data]
  (zipmap data-keys (repeat [])))

(defn append-map-to-result
  [cum-map item-map]
  (reduce (fn [result map-entry]
            (let [[curr-key curr-val] map-entry]
              (update-in result [curr-key] conj curr-val)))
    cum-map
    item-map))

(defn transform-data
  [data]
  (reduce
    (fn [cum-result curr-map]
      (append-map-to-result cum-result curr-map))
    (create-empty-result data)
    data))

有结果:

(dotest
  (is= (create-empty-result data)
    {:id [], :first-name [], :last-name [], :age []})

  (is= (append-map-to-result (create-empty-result data)
         {:id 1 :first-name "John1" :last-name "Dow1" :age "14"})
    {:id [1], :first-name ["John1"], :last-name ["Dow1"], :age ["14"]})

  (is= (transform-data data)
    {:id         [1 2 3 4 5],
     :first-name ["John1" "John2" "John3" "John4" "John5"],
     :last-name  ["Dow1" "Dow2" "Dow3" "Dow4" "Dow5"],
     :age        ["14" "54" "34" "12" "24"]}))

请注意,我包括了针对辅助功能的单元测试,既可以记录它们的预期功能,又可以向读者表明它们实际上像广告中所宣传的那样。