【问题标题】:Has a value increased in a Clojure vector of hashes?Clojure 哈希向量中的值是否增加了?
【发布时间】:2014-02-04 07:22:56
【问题描述】:

新手 Clojure 问题提醒...

我有一个 Clojure 向量,看起来像这样:

(def sample-data
  [{:date "2014-01-01" :value 5}
   {:date "2014-01-02" :value 7}
   {:date "2014-01-03" :value 6}
   {:date "2014-01-04" :value 7}
   {:date "2014-01-07" :value 11}])

实际上它比这要大得多,但你明白了一般的想法...... - 它是一个单一值的向量,每天收集一次。向量将按 :date 顺序排序,但奇数缺失样本存在间隙。

我想创建一个函数,该函数在“样本数据”中获取一个 :date,并告诉我该日期的 :value 是否大于前一个日期的 :value。角落案例:

  • 如果在前一个日期没有样本,那么我想回到收集样本的最后日期并与那个 :value 进行比较
  • 如果指定日期没有 :value,那么我很乐意提出错误并在其他地方处理
  • 如果两个:values相等,我想返回false(因为它没有增加)

我想调用函数,例如

(value-increased? sample-data {:date '2014-01-03'})

并得到一个布尔响应。在这种情况下,它会是错误的,因为 2014-01-03 (6) 的值小于前一天 (7) 的值

提前致谢

【问题讨论】:

    标签: vector hash clojure


    【解决方案1】:

    您可以使用二进制搜索找到给定日期的索引,然后检查该索引处的条目和前一个索引。

    或者,您可以使用 mikera 的 timeline 库,该库提供了一种数据结构,可以准确地维护这种带时间戳值的类向量日志以及在此类日志上运行的各种函数:

    (require '[mikera.timeline :as tl])
    
    (def t
      (-> (tl/timeline)
          ;; (tl/log timestamp value)
          (tl/log 0 0)
          (tl/log 86400000 1)))
    

    然后tl/seek可以让你找到给定时间之前最后一个条目的索引,而给定索引处的条目可以用nth提取。

    最后,clj-time 可用于指定时间(并且已经是时间线的依赖项):

    (tl/log (tl/timeline) (clj-time.core/now) :foo)
    ;= #<Timeline [[#<Instant 2014-02-04T08:00:08.290Z> :foo]]>
    

    【讨论】:

    • 又是一个不错的解决方案,但不如第一个好。谢谢
    【解决方案2】:
    (defn has-value-increased?
      [sample searched-date]
      (reduce (fn [_ [{:keys prev-value :value} {:keys [value date]}]]
                (if (and (= date searched-date)
                         (< prev-value value))
                  (reduced true)))
              nil
              (partition 2 1 sample)))
    

    请注意,这可能效率低下,因为每次调用函数时都可能遍历整个样本。根据实际应用程序,最好编写一个函数来收集值在单程中增加的所有日期并将其返回 e。 G。作为一个集合,您可以在其中调用它作为一个函数来检查日期是否增加了。

    这是修改后的函数:

    (defn collect-dates-with-increased-value
      [sample]
      (reduce (fn [acc [{prev-value :value} {:keys [date value]}]]
                (cond-> acc
                  (< prev-value value) (conj date)))
              []
              (partition 2 1 sample)))
    

    然后收集日期,例如。 G。在 REPL:

    => (def dates-with-increased-value (set (collect-dates-with-increased-value sample-data)))
    => dates-with-increased-value
    #{"2014-01-02" "2014-01-04" "2014-01-07"} 
    => (dates-with-increased-value "2014-01-02")
    "2014-01-02"
    => (dates-with-increased-value "2014-01-03")
    nil 
    

    【讨论】:

    • 会选择这个答案,因为它简单而优雅。即使我没有要求它,即使我没有要求它,能够获得完整的日期集实际上对我来说非常有用 - 我将遍历所有日期,但是您进行一次计算的方法然后从结果集中提取数据似乎是一个特别好的解决方案。谢谢
    • 仅供参考,我简化了算法,以便输入序列无论哪种类型都不需要反转。
    【解决方案3】:

    您是否有理由不能使用排序集而不是向量?如果集合实际上是按日期排序的,并且 Clojure 知道这一点,您可以简单地使用 get 查找特定日期,或使用 subseq/rsubseq 查找某个目标附近的日期。然后很容易检查你想要的任何日期的任何条件。

    【讨论】:

    • 4 个很好的答案之一,但不是我在这种情况下要使用的答案。我可以将数据整理成一个排序集,这样您的解决方案就可以正常工作,但我认为第一个答案最适合我描述的问题。谢谢
    【解决方案4】:

    如果你尝试这段代码,你可以从(first..)开始注释每一行,看看哪一步做了什么。

    (->>
      sample-data
      (filter :value)
      (map #(assoc % :date (.parse (java.text.SimpleDateFormat. "yyyy-MM-dd") (:date %))))
                         ;parse date
      (sort-by :date)
      (partition 2 1)    ;partition the sequence into every two consecutive days that have a :date value
      (map (fn [[f s]]   ;destructure vector into first day f and second day s 
             (conj s [:in-order (< (:value f) (:value s))])))
                         ;compare days, return second date with compare boolean in in :in-order key 
      (remove :in-order) ;filter where :in-order is false
      first              ;take first false :in-order, and stop processing
    )
    

    【讨论】:

    • 我会离开排序,否则最终的 zipmap 可能会分配错误的键/值。
    • 你的算法,现在看起来,将返回具有最早日期的哈希映射,紧随其后的是具有更高值的日期...:O
    • 很好,如果比较结果为真,现在您可以在 lambda 中简单地返回 s,然后使用 keep 而不是 map。将摆脱 :in-order。
    • 在当前版本中,我猜你必须再次使用filter 而不是remove
    • 我可以很好地遵循您的逻辑,我可以看到您的解决方案是如何结合在一起的,但在我看来,这不是最好的答案(虽然它非常好;我只是得到了一堆很棒的解决方案!)。还是谢谢
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2022-01-09
    • 1970-01-01
    • 2011-06-03
    • 1970-01-01
    • 1970-01-01
    • 2012-03-14
    相关资源
    最近更新 更多