我是Clojure的新手,作为学习语言的一种练习,我正在用Clojure重写我的一个古老的常规脚本。对于上下文,脚本在JIRA实例中查询时间条目,在json中接收结果,并根据响应生成报告。
我意识到关于嵌套结构遍历的问题已经无限次地被问到了,但是我未能找到对此的直接答案,因此我希望惯用的简洁方法能帮助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的示例结构可以在here中找到)
请注意,在创建结果时间条目时,我们从嵌套结构的最外层使用
issue.key
,从中间层使用date
,从嵌套结构的最内层使用from
和to
。在groovy中,
return
循环内的each
仅存在于最内部。我相信其余的代码应该或多或少地自我解释。因此,我要解决的一般问题是:鉴于地图和列表的嵌套结构很深:
我发现这种使用上下文遍历和转换数据的方式已经越来越普遍。
我当前的解决方案比普通的解决方案更为冗长,对于我未经训练的阅读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))))
最佳答案
我认为您的Clojure代码一点也不差。这就是我要改善的方式。只是一些更改。
(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? [history from-date to-date]
(tcore/within? (tcore/interval from-date to-date)
(tformat/parse (tformat/formatters :date-time) (:created history))))
(defn valid-history? [h from-date to-date]
(and (history-has-time-items? h) (history-in-date-range? h from-date to-date)))
(defn issue->histories-with-key [issue]
(map #(assoc % :issue-key (:key issue)) (get-in issue [:changelog :histories])))
(defn time-item->time-entry [item date issue-key]
(let [get-int (fn [k] (Integer/parseInt (get item k 0)))]
{:date date
:issue-key issue-key
:seconds-spent (- (get-int :to) (get-int :from))}))
(defn history->time-entries [history]
(map #(time-item->time-entry % (:created history) (:issue-key history)) (history->time-items history)))
(defn json->time-entries [json opts]
(let [{:keys [from-date to-date]} opts]
(->> json
:issues
(mapcat issue->histories-with-key)
(filter #(valid-history? % from-date to-date))
(mapcat #(history->time-entries %)))))
(defn generate-time-report [opts]
(json->time-entries opts (query-jira->json opts)))
密钥更改
json->time-entries
实现。现在很清楚json
如何变成time-entries
。例如。 json->问题->历史->时间输入mapcat
代替(flatten (map ...
:from-date
和:to-date
。我认为将from-date
和to-date
发送到函数中会使函数签名比opts
更加可读json
)和opts
的位置。将最重要的参数放在第一个位置,除非它的收集函数接受lambda之类的像map
,filter
等。