【问题标题】:Clojure convert vector of maps into map of vector valuesClojure 将映射向量转换为向量值映射
【发布时间】:2019-01-25 14:39:47
【问题描述】:

有一些与此主题相关的 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 中是否有一种优雅而有效的方法来做到这一点?

【问题讨论】:

  • 您可以使用 reduce 来执行此操作,但不确定它有多优雅。 :D
  • 是的。我认为 Clojure 会提供比直接 reduce 更优雅的解决方案。

标签: clojure


【解决方案1】:

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

(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"]]}

【讨论】:

  • 我怀疑 group-by 可以发挥重要作用,但无法使其发挥作用。你的速度更快,干得好:)
【解决方案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))))

【讨论】:

    【解决方案3】:

    对我来说,最清晰的方法如下:

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

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

    【讨论】:

    • 如果键是集合则不起作用。问题是merge-with 不允许您指定起始值。这些天来,我认为我们希望这是合并函数的零参数返回的值。
    【解决方案4】:

    如果我们将地图连接成单个对序列,我们就有了一个图的边列表表示。我们要做的就是将其转换为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】:

      写代码时请考虑读者!打“代码高尔夫”没有奖品。但是,当您强迫他人破译过度压缩的代码时,他们会付出相当大的代价。

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

      请注意,我包含了辅助函数的单元测试,既可以记录它们的意图,也可以向读者展示它们实际上像宣传的那样工作。

      This template project可以用来运行上面的代码。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2022-01-08
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2016-07-22
        • 2023-03-25
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多