【问题标题】:Adding key- value pairs to maps in a list of maps from another list of maps in clojure将键值对添加到 clojure 中另一个映射列表中的映射列表中的映射
【发布时间】:2016-07-20 07:41:06
【问题描述】:

我有一张地图列表

 ( {:path "first" :size "1 gb"}  
   {:path "second" :size "500 mb"}
  ...)

还有另一个地图列表

 ( {:path "first" :size "1 gb" :date "1"}
   {:path "second" :size "500 mb" :date "1"}
   {:path "first" :size "0.9 gb" :date "2"}...
   {:path "second" :size "400 mb" :date "2"}...
 ...)

我想将第一个地图列表转换为类似的东西

( {:path "first" :sizeon1 "1 gb" :sizeon2 "0.9 gb"...}
  {:path "second" :sizeon1 "500 mb" :sizeon2 "400 mb"...}
  ....)

我是 Clojure 菜鸟,很难做到这一点。 你能帮帮我吗?

【问题讨论】:

  • 在您的示例中,您实际上并不需要第一个列表,因为它的数据在第二个列表中重复。是否总是如此,或者是否存在某些路径值出现在第一个列表中而在第二个列表中不存在的情况,反之亦然?如果是这样,这些情况的预期结果是什么?
  • 是的,这总是正确的。没错,我只需要一个新列表,其中包含第二个列表中所需的结果。第二个列表按日期排序..即所有日期为“1”的条目,然后是“2”,依此类推(编辑)..怎么做?另外,你能看看 superkonduktr 的回答并回答我的评论吗?

标签: clojure clojure-java-interop clojure-contrib


【解决方案1】:

我会做什么,是重新考虑生成的数据结构: 我不知道您将如何使用生成的集合,但命名键 :sizeonX,尤其是当注册日期的数量可能可变或者其中一些可能丢失时(例如,如果您有日期 1 3 用于第一个路径,1 2 3 5 用于第二个路径)会导致结果映射中出现一堆不可预测的命名键,这会使检索变得更加困难这些钥匙。 在我看来,使用这种结构会更好:

{:path "first" :sizes {"1" "500" "2" "1g" "10" "222"}}

所以这个尺寸图很容易迭代和处理。

我会怎么做:

(def data '({:path "first" :size "1 gb" :date "1"}
            {:path "first" :size "0.9 gb" :date "3"}
            {:path "second" :size "500 mb" :date "1"}
            {:path "second" :size "700 mb" :date "2"}
            {:path "second" :size "400 mb" :date "3"}
            {:path "second" :size "900 mb" :date "5"}))

(map (fn [[k v]] {:path k
                  :sizes (into {} (map (juxt :date :size) v))})
     (group-by :path data))

;; ({:path "first", :sizes {"1" "1 gb", "3" "0.9 gb"}} 
;;  {:path "second", :sizes {"1" "500 mb", 
;;                           "2" "700 mb", 
;;                           "3" "400 mb", 
;;                           "5" "900 mb"}})

更新

但由于您仍然需要问题的结构,我会这样做:

(map (fn [[k v]]
       (into {:path k}
             (map #(vector (keyword (str "sizeon" (:date %)))
                           (:size %))
              v)))
     (group-by :path data))

;;({:path "first", :sizeon1 "1 gb", :sizeon3 "0.9 gb"} 
;; {:path "second", 
;;  :sizeon1 "500 mb", :sizeon2 "700 mb", 
;;  :sizeon3 "400 mb", :sizeon5 "900 mb"})

基本上类似于@superkonduktr 变体。

【讨论】:

  • 很抱歉,我需要以键的形式使用它们,因为它们实际上是表中的列。有没有办法把它转换成这样的形式?还是其他方式?
  • 还有一件事,我怎样才能重新排序这个函数的输出,以便它首先有键 :path 然后 :sizeon1 , :sizeon2...等等?目前它在末尾显示 :path。
  • 那是因为哈希映射是无序的。一般来说,您无法预测键在地图中的位置。但是,在这种情况下,为什么您需要对键进行排序呢?你仍然可以使用sorted-map,如果有真正需要(我想没有)
  • 所以如果你需要它,把{:path k} 行替换为 accumulator sorted-map: (sorted-map-by #(cond (= :path %1) -1 (= :path %2) 1 :else (compare %1 %2)))
【解决方案2】:

当您将任务分解为更小的部分时,一切都会变得清晰。

首先,定义一个助手来在结果数据集中创建那些:sizeon1 键:

(defn date-key
  [date]
  (keyword (str "sizeon" date)))

接下来,您希望将单个路径数据的集合缩减为聚合地图,假设这样的集合看起来如您所描述:

[{:path "first" :size "1 gb" :date "1"}
 {:path "first" :size "0.9 gb" :date "2"}
 ;; ...
 ]

reduce 只是用于此目的的工具:

(defn reduce-path
  [path-data]
  (reduce
    ;; A function that takes an accumulator map and an element in the collection
    ;; from which you take date and size and assoc them under the appropriate keys
    (fn [acc el]
      (let [{:keys [date size]} el]
        (assoc acc (date-key date) size)))
    ;; A starting value for the accumulator containing the common path
    ;; for this collection
    {:path (:path (first path-data))}
    ;; The collection of single path data to reduce
    path-data))

最后,获取包含不同路径的原始数据集,按路径对其进行分区,并将reduce-path 函数映射到其上。

(def data
  [{:path "first" :size "1 gb" :date "1"}
   {:path "first" :size "0.9 gb" :date "2"}
   {:path "second" :size "500 mb" :date "1"}
   {:path "second" :size "400 mb" :date "2"}])

(->> data
     (partition-by :path)
     (map reduce-path))

请注意,此代码假定您的初始 data 集合已按 :path 排序。否则,partition-by 将无法按预期工作,并且必须相应地准备数据。

【讨论】:

  • 非常感谢!正如你所说,如果集合不是按:path 排序的,partition-by 将不起作用,我的数据是这样的,所有的地图都首先是:date“1”,然后是:date“2”等等......你能告诉我什么遇到这种情况怎么办?
  • 在这种情况下,您应该将(partition-by :path) 行替换为两行:(group-by :path)vals。这具有相同的效果,因为 group-by 将项目分组到一个映射中,其中值是类似于 partition-by 结果的集合
  • 除了@leetwinski 的建议之外,值得一提的是,您的数据缺少一个合理的(理想情况下是数字)字段来执行排序。然后,您可以使用(sort-by :sortable-key collection) 来确保在将集合提供给归约函数之前的正确顺序。使用示例请参考clojuredocs.org/clojure.core/sort-by
  • 我已经使用 sort-by 创建了一个函数。
【解决方案3】:
(def data '({:path "first" :size "1 gb" :date "1"}
            {:path "second" :size "500 mb" :date "1"}
            {:path "first" :size "0.9 gb" :date "2"}
            {:path "second" :size "400 mb" :date "2"}))

(defn- reduce-group [g]
  (reduce (fn [acc m] (assoc acc
                             (keyword (str "sizeon" (:date m)))
                             (:size m)))
          (first g) g))

(let [groups (group-by :path data)]
  (map reduce-group (vals groups)))

【讨论】:

  • 我猜这是错误的,因为操作可能想根据它们的 :date 值命名键(可能还有更多。(:size3 等)
猜你喜欢
  • 2011-12-04
  • 1970-01-01
  • 2021-09-21
  • 1970-01-01
  • 2021-01-16
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-12-21
相关资源
最近更新 更多