我意识到在S.O上已经有无数关于遍历嵌套结构的问题,但是我没有找到直接的答案,因此我希望得到Clojurist的帮助,找到一种惯用且简洁的方法。核心问题是通用的,与这个特定的代码片段无关。
我希望用Clojure重写以下内容:
// GROOVY CODE
// this part is just here for context
def timeFormat = DateTimeFormat.forPattern('yyyy/MM/dd')
def fromDate = timeFormat.parseDateTime(opts.f)
def toDate = timeFormat.parseDateTime(opts.t)
def json = queryJiraForEntries(opts, http)
def timesheets = [:].withDefault { new TimeSheet() }
// this is what I'm hoping to find a better way for
json.issues.each { issue ->
issue.changelog.histories.each { history ->
def date = DateTime.parse(history.created)
if (date < fromDate || date > toDate) return
def timeItems = history.items.findAll { it.field == 'timespent' }
if (!timeItems) return
def consultant = history.author.displayName
timeItems.each { item ->
def from = (item.from ?: 0) as Integer
def to = (item.to ?: 0) as Integer
timesheets[consultant].entries << new TimeEntry(date: date, issueKey: issue.key, secondsSpent: to - from)
}
}
}
(可以在这里找到返回的JSON的示例结构)
请注意,当我们创建结果时间条目时,我们使用最外层的issue.key
,中间层的date
,以及嵌套结构的最内层的from
和to
。
在Groovy中,each
循环中的return
仅存在于最内部的each
中。我相信其余的代码应该更或多或少是不言自明的。
因此,我试图解决的通用问题是:给定一个深度嵌套的映射和列表结构:
- 遍历/筛选到结构的特定深度
- 在该深度级别上执行某些操作,并将结果添加到上下文中
- 更深入地遍历/筛选结构
- 在该深度级别上执行某些操作,并将结果添加到上下文中
- ...
- 在某个最终级别上,根据上下文中的数据和该级别可用的数据生成结果。
我发现这种带有上下文的遍历和转换数据的模式越来越常见。
我的当前解决方案比groovy的更冗长,对于我这种不擅长阅读clojure代码的人来说,一眼看去要难得多。解析日期等细节并不重要。我正在寻找一个简洁的clojure模式。
编辑1:根据评论请求,这是我的当前代码。我提前道歉,并无耻地归咎于我完全的新手:
;; CLOJURE CODE
(defn valid-time-item? [item]
(and (= (:field item) "timespent") (:to item) (:from item)))
(defn history->time-items [history]
(filter valid-time-item? (:items history)))
(defn history-has-time-items? [history]
(not-empty (history->time-items history)))
(defn history-in-date-range? [opts history]
(tcore/within? (tcore/interval (:from-date opts) (:to-date opts))
(tformat/parse (tformat/formatters :date-time) (:created history))))
(defn valid-history? [opts h]
(and (history-has-time-items? h) (history-in-date-range? opts h)))
(defn issue->histories-with-key [issue]
(map #(assoc % :issue-key (:key issue))(get-in issue [:changelog :histories])))
(defn json->histories [opts json]
(filter #(valid-history? opts %) (flatten (map issue->histories-with-key (:issues json)))))
(defn time-item->time-entry [item date issue-key]
(let [get-int (fn [k] (Integer/parseInt (get item k 0)))]
{:date (tformat/unparse date-formatter date)
:issue-key issue-key
:seconds-spent (- (get-int :to) (get-int :from)) }))
(defn history->time-entries [opts history]
(let [date (tformat/parse (tformat/formatters :date-time) (:created history))
key (:issue-key history)]
(map #(time-item->time-entry % date key) (history->time-items history))))
(defn json->time-entries [opts json]
(flatten (map #(history->time-entries opts %) (json->histories opts json))))
(defn generate-time-report [opts]
(json->time-entries opts (query-jira->json opts)))
为了简洁起见,上述代码省略了某些脚手架等内容。上述中的入口点是generate-time-report
,它返回一组映射。
在issue->histories-with-key
中,我通过将问题键插入到每个历史记录映射中来保留issue.key
上下文。除了代码的一般结构外,这是我发现丑陋和不可扩展的地方之一。此外,我还没有将consultant
维度添加到clojure解决方案中。
编辑2:经过一些调整和评论以及下面答案的输入后,进行了第二次尝试。这个更短,使用了更接近原始代码的结构,并包含原始代码中的consultant
部分。
;; CLOJURE CODE - ATTEMPT 2
(defn create-time-entry [item date consultant issue-key]
(let [get-int #(Integer/parseInt (or (% item) "0"))]
{:date (f/unparse date-formatter date)
:issue-key issue-key
:consultant consultant
:seconds-spent (- (get-int :to) (get-int :from)) }))
(defn history->time-entries [history issue-key from-date to-date]
(let [date (f/parse (f/formatters :date-time) (:created history))
items (filter #(= (:field %) "timespent") (:items history))
consultant (get-in history [:author :displayName])]
(when (and (t/within? (t/interval from-date to-date) date) (not-empty items))
(map #(create-time-entry % date consultant issue-key) items))))
(defn issue->time-entries [issue from-date to-date]
(mapcat #(history->time-entries % (:key issue) from-date to-date)
(get-in issue [:changelog :histories])))
(defn json->time-entries [json from-date to-date]
(mapcat #(issue->time-entries % from-date to-date) (:issues json)))
(defn generate-time-report [opts]
(let [{:keys [from-date to-date]} opts]
(filter not-empty
(json->time-entries (query-jira->json opts) from-date to-date))))
->
看起来是一种推荐的做法。你有什么看法? - Matias Bjarland