创建新地图或更新现有地图

时间:2016-01-21 16:55:39

标签: clojure

我有以下数据:

({:seriesId "series 0", :episodeId "0"}
 {:seriesId "series 1", :episodeId "1"}
 {:seriesId "series 1", :episodeId "2"}
 {:seriesId "series 2", :episodeId "3"}
 {:seriesId "series 2", :episodeId "4"}
 {:seriesId "series 2", :episodeId "5"})

并希望将每集与其系列相关联,如下所示:

[{:put-request
  {:item {:seriesId "series 0", :episodeCount 1, :episodeIds #{"0"}}}}
 {:put-request
  {:item {:seriesId "series 1", :episodeCount 2, :episodeIds #{"1" "2"}}}}
 {:put-request
  {:item {:seriesId "series 2", :episodeCount 3, :episodeIds #{"3" "4" "5"}}}}]

目前我遇到以下情况:

[{:put-request
  {:item {:seriesId "series 0", :episodeCount 1, :episodeIds #{"0"}}}}
 {:put-request
  {:item {:seriesId "series 1", :episodeCount 1, :episodeIds #{"1"}}}}
 {:put-request
  {:item {:seriesId "series 1", :episodeCount 1, :episodeIds #{"2"}}}}
 {:put-request
  {:item {:seriesId "series 2", :episodeCount 1, :episodeIds #{"3"}}}}
 {:put-request
  {:item {:seriesId "series 2", :episodeCount 1, :episodeIds #{"4"}}}}
 {:put-request
  {:item {:seriesId "series 2", :episodeCount 1, :episodeIds #{"5"}}}}]

我正在使用create-or-update-series功能。我不知道如何使用seriesId查找/获取以前添加的系列(如果已添加!)。我尝试了很多东西,但这些都是死路一条。

(ns clojure-sscce.core
  (:gen-class)
  (:require clojure.pprint))

(defn create-or-update-series
  ([episodes]
    (create-or-update-series episodes []))
  ([episodes result]
    (if (zero? (count episodes))
      result
      (create-or-update-series (rest episodes)
        (conj result {
          :put-request {
            :item {
              :seriesId (:seriesId (first episodes))
              :episodeCount 1
              :episodeIds #{(:episodeId (first episodes))}}}})))))

;; Tests
(defn -main [& args]
  (let 
    [series0 (mapv (fn [episode-id] {
      :seriesId "series 0"
      :episodeId (str episode-id)}) (range 0 1))
    series1 (mapv (fn [episode-id] {
      :seriesId "series 1"
      :episodeId (str episode-id)}) (range 1 3))
    series2 (mapv (fn [episode-id] {
      :seriesId "series 2"
      :episodeId (str episode-id)}) (range 3 6))]

    (clojure.pprint/pprint
      (concat series0 series1 series2))

    (clojure.pprint/pprint 
      (create-or-update-series (concat series0 series1 series2)))))

请注意,{:put-request {:item { ...是必需的,因为新地图应该是PUT到DynamoDB。

非常乐意帮助你!

2 个答案:

答案 0 :(得分:5)

group-by非常适合这样的事情。这是尝试与for理解相结合的尝试:

(defn group-by-series [episodes]
  (let [grouped (group-by :seriesId episodes)]
    (for [[series eps-in-series] grouped]
      {:seriesId series 
       :episodeCount (count eps-in-series)
       :episodeIds (into #{} (map :episodeId eps-in-series))})))

(group-by-series example-data)
;=> ({:seriesId "series 0", :episodeCount 1, :episodeIds #{"0"}} 
;    {:seriesId "series 1", :episodeCount 2, :episodeIds #{"1" "2"}}
;    {:seriesId "series 2", :episodeCount 3, :episodeIds #{"3" "4" "5"}})

如果需要,您可以在for理解中添加DynamoDB内容,或者创建包装函数并将其映射到它们之间。

答案 1 :(得分:1)

因此,如果我们想要查看“创建或更新”问题,我们可以通过几种方式来实现它。就像你的尝试一样,我们需要递归地创建一系列的系列,但是像group-by一样,最好将它设为 map ,以系列ID为基础。这样,当我们在输入中找到新的剧集时,我们可以轻松有效地找到它在集合中所属的系列。

首先,让我们做一些便利功能,只为一集更新这样的地图。它应该:

  • 拍摄一系列地图和一集。
  • 查找正确的系列,如果它在那里,或者创建一个。
  • 将剧集添加到剧集中,将剧集添加到剧集地图中。

这是我的方法:

(defn- update-series-map [series-map {:keys [seriesId episodeId] :as episode}]
  (let[current-series (get series-map seriesId 
                           {:seriesId seriesId :episodeIds #{} :episodeCount 0})
       updated-series (-> current-series
                          (update-in [:episodeCount] inc)
                          (update-in [:episodeIds] conj episodeId))]
    (assoc series-map seriesId updated-series)))

如果系列还没有条目,我们可以使用get if-not-found 参数创建一个合适的空系列,否则我们得到的条目是那里。在任何一种情况下,我们都必须更新条目以添加剧集 - 我们必须conj剧集ID进入剧集集并inc剧集计数。我使用update-in来完成这两项工作,但是如果您使用的是Clojure 1.7 + update则更适合这样的情况,我们不会使用比1键更深的键序列。

通过这个构建块,我们可以制作一些内容来循环播放几集。我们可以使用像create-or-update-series

这样的多元递归方法来实现
(defn group-by-series-multiarity 
  ([episodes]
   (group-by-series-multiarity {} episodes))
  ([series-map 
    [ep & more]]
   (if (seq more)
     (recur (update-series-map series-map ep) more)
     (vals (update-series-map series-map ep)))))

在结构上这基本相同。我使用recur而不是通过名称重复主要作为优化。显式调用会占用调用堆栈空间,而recur可以避免这种情况。使用seq检查空虚是另一个小优化,因为我们不必遍历剩余的剧集来计算它们。

最后它需要一点清理,因为我们不想要我们创建的整个地图,只需要值。这就是我最后vals的原因。

或者,我们可以使用loop作为recur的目标。如果我们的“公共API”不适合我们递归的方式,这可能会很好:

(defn group-by-series-looping[episodes]
  (loop[series-map {}
        [ep & more] episodes]
    (if (seq more)
      (recur (update-series-map series-map ep) more)
      (vals (update-series-map series-map ep)))))

loop基本上就像创建本地帮助函数(在本例中为arity 2)并在其中使用recur一样。

我们还可以注意到这些递归函数遵循a well-known pattern called 'left fold' or 'reduction'并使用高阶函数抽象该模式:

(defn group-by-series-reducing [episodes]
  (vals (reduce update-series-map {} episodes)))

请注意reduce如果我们只提供它应该使用的缩减功能loop基本上会处理整个group-by-series-looping update-series-map {} {{1}} })和初始值{{1}}。