Clojure迭代JSON并更新地图中的各个键

时间:2018-01-06 16:36:17

标签: json clojure

我是Clojure的新手,在尝试了多种方法后,我完全陷入困境。我知道如何在任何其他命令式语言中实现这一点,但不是在Clojure中。

我有一个包含流星坠落数据的JSON文件https://data.nasa.gov/resource/y77d-th95.json,每个秋天包含一个质量和年份。

我试图找出哪一年的总体跌幅最大。

这是我到目前为止所拥有的:

(def jsondata
(json/read-str (slurp "https://data.nasa.gov/resource/y77d-th95.json") :key-fn keyword))

;Get the unique years
(def years (distinct (map :year jsondata)))
;Create map of unique years with a number to hold the total mass
(def yearcount (zipmap years (repeat (count years) 0)))

我的想法是使用for函数迭代jsondata,并使用相应的键(fall对象中的年份)更新yearcount映射,并使用对象的质量(将其递增,如在C中的+ =) )

我试过这个,虽然我知道它可能不起作用:

(for [x jsondata]
    (update yearcount (get x :year) (+ (get yearcount (get x :year)) (Integer/parseInt (get x :mass)))))

当然这个想法是年份数据图将保存每年的总数,然后我可以使用频率,排序,并最后获得质量最高的年份。

还定义了这个函数来更新地图中带有函数的值,虽然我不确定我是否真的可以使用它:

(defn map-kv [m f]
  (reduce-kv #(assoc %1 %2 (f %3)) {} m))

我尝试了一些不同的方法,遇到了很多问题而且无法到达任何地方。

3 个答案:

答案 0 :(得分:1)

这是一个替代版本,只是为了展示一种略有不同风格的方法。特别是如果你是clojure的新手,可能更容易看到导致解决方案的逐步思考。

棘手的部分可能是for语句,这是通过(在这种情况下)将函数应用于现有映射中的每个键和值来构建新集合的另一种好方法。

(defn max-meteor-year [f]
  (let [rdr (io/reader f)
        all-data (json/read rdr :key-fn keyword)
        clean-data (filter #(and (:year %) (:mass %)) all-data)
        grouped-data (group-by #(:year %) clean-data)
        reduced-data
        (for [[k v] grouped-data]
          [(subs k 0 4) (reduce + (map #(Double/parseDouble (:mass %)) v))])]
    (apply max-key second reduced-data)))

clj.meteor> (max-meteor-year "meteor.json")
["1947" 2.303023E7]

答案 1 :(得分:1)

这是我的解决方案。我认为你会喜欢它,因为它的部分是分离的,并没有加入到单个的treading宏中。因此,当出现问题时,您可以更改并测试它的任何部分。

获取数据:

(def jsondata
  (json/parse-string
   (slurp "https://data.nasa.gov/resource/y77d-th95.json")
   true))

请注意,您可以只传递true标记,指示键应该是关键字而不是字符串。

声明一个辅助函数,该函数考虑了第一个参数缺失的情况(为零):

(defn add [a b]
  (+ (or a 0) b))

声明一个reduce函数,它从一组流星数据中获取结果和一个项目。它使用我们之前创建的add函数更新结果映射。请注意,有些商品没有massyear个键;在对它们进行操作之前,我们应该检查它们是否存在:

(defn process [acc {:keys [year mass]}]
  (if (and year mass)
    (update acc year add (Double/parseDouble mass))
    acc))

最后一步是运行缩减算法:

(reduce process {} jsondata)

结果是:

{"1963-01-01T00:00:00.000" 58946.1,
 "1871-01-01T00:00:00.000" 21133.0,
 "1877-01-01T00:00:00.000" 89810.0,
 "1926-01-01T00:00:00.000" 16437.0,
 "1866-01-01T00:00:00.000" 559772.0,
 "1863-01-01T00:00:00.000" 33710.0,
 "1882-01-01T00:00:00.000" 314462.0,
 "1949-01-01T00:00:00.000" 215078.0,

我认为这种逐步解决方案比单个巨大的->>线程更清晰,更易于维护。

答案 2 :(得分:0)

更新:抱歉,我误解了这个问题。我认为这对你有用:

(->> (group-by :year jsondata)
     (reduce-kv (fn [acc year recs]
                  (let [sum-mass (->> (keep :mass recs)
                                      (map #(Double/parseDouble %))
                                      (reduce +))]
                    (assoc acc year sum-mass)))
                {})
     (sort-by second)
     (last))
=> ["1947-01-01T00:00:00.000" 2.303023E7]

此处的reduce函数以一个初始空映射开始,其输入将是group-by的输出,它是从年份到相应记录的映射。

对于reduce的每个步骤,reduce功能都会收到acc地图,我们正在构建,当前的year键以及相应的那一年recs。然后,我们从:mass获取所有recs值(使用keep代替map,因为并非所有recs显然都具有质量值。然后我们用Double/parseDouble映射它以将质量字符串解析为数字。然后我们reduce通过<{>>总和所有recs的所有质量。最后,我们assoc year acc的{​​{1}}密钥与sum-mass。这会输出从年份到质量总和的地图。

然后我们可以按照它们的值对这些映射键/值对进行排序(second返回值),然后我们取最后一个具有最高值的项。