【问题标题】:Create new map or update existing one创建新地图或更新现有地图
【发布时间】:2016-08-11 18:03:42
【问题描述】:

我有以下数据:

({: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。

希望得到您的帮助!

【问题讨论】:

  • 您是否喜欢使用字符串作为值?如果您可以使用整数,那可能会使这项任务更容易。
  • 是的,seriesIdepisodeId 必须是字符串。

标签: clojure


【解决方案1】:

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 内容,或者制作一个包装函数并将其映射到它们之间。

【讨论】:

  • 通过在group-by 中使用现成的实现,这在某种程度上回避了“创建或更新”的问题。我也可以尝试并展示一种方法。
【解决方案2】:

因此,如果我们想这样看待“创建或更新”问题,我们可以通过几种方法来实现它。就像您的尝试一样,我们将需要递归地制作一系列系列,但就像 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)))

这里我们可以使用getif-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 基本上是如何处理来自group-by-series-looping 的整个loop 如果我们只给它它应该使用的reducing 函数 (update-series-map) 和初始值@987654345 @。

【讨论】:

  • 优秀。我喜欢这样一个事实,即我们使用单个 get 获取/创建,然后在任何情况下更新系列地图。非常感谢您提供第二个解决方案,这就是我要使用的解决方案。
猜你喜欢
  • 1970-01-01
  • 2019-05-20
  • 2018-07-09
  • 1970-01-01
  • 2020-12-30
  • 1970-01-01
  • 2010-11-07
  • 1970-01-01
  • 2012-08-25
相关资源
最近更新 更多