有一些与此主题相关的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中,有一种优雅而有效的方法吗?
答案 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"]}))
请注意,我包括了针对辅助功能的单元测试,既可以记录它们的预期功能,又可以向读者表明它们实际上像广告中所宣传的那样。