代码之家  ›  专栏  ›  技术社区  ›  frank

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

  •  1
  • frank  · 技术社区  · 6 年前

    有一些关于这个主题的这样的帖子,但是我找不到任何适合我想要完成的工作。

    我有一个地图矢量。我将使用另一篇相关的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 回复  |  直到 6 年前
        1
  •  5
  •   Michiel Borkent    6 年前

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

    (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"]]}
    
        2
  •  2
  •   Brandon    6 年前

    嗯,我想出了一个解决方案,在我发帖之前,Michiel发布了一个更简洁的解决方案,但我还是会继续发布它=。

    (defn map-coll->key-vector-map
      [coll]
      (reduce (fn [new-map key] 
                (assoc new-map key (vec (map key coll))))
              {}
              (keys (first coll))))
    
        3
  •  0
  •   glts    6 年前

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

    (defn ->coll [x]
      (cond-> x (not (coll? x)) vector))
    
    (apply merge-with #(conj (->coll %1) %2) data)
    

    基本上,这里的任务是合并所有地图( merge-with ,并通过连接在同一个键上收集所有值( conj )在键“确保值实际连接到向量上”的向量上( ->coll )

        4
  •  0
  •   Thumbnail    6 年前

    如果我们将地图连接成一个成对的序列,我们就得到了一个图的边列表表示。我们要做的是把它转换成 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"]}
    

    与其他方法相比,此方法的优点在于给定序列中的地图可以是不同的形状。

        5
  •  -1
  •   Alan Thompson    6 年前

    编写代码时请考虑读者!玩“代码高尔夫”没有奖品。但是,当您强制他人破译过度浓缩的代码时,会给他们带来相当大的成本。

    我总是试图明确代码在做什么。如果您将问题分解为简单的步骤并使用好的名称,这是最简单的。尤其是,几乎不可能用 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"]}))
    

    请注意,我包含了辅助函数的单元测试,这是一种既记录它们要做什么的方法,又向读者演示它们实际上是按照广告的方式工作的方法。