我是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,从嵌套结构的最内层使用fromto

在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-dateto-date发送到函数中会使函数签名比opts更加可读
  • 交换主题(json)和opts的位置。将最重要的参数放在第一个位置,除非它的收集函数接受lambda之类的像mapfilter等。
  • 10-06 07:36
    查看更多