【问题标题】:Date periods in clojureclojure 中的日期期间
【发布时间】:2017-05-28 22:45:52
【问题描述】:

我有这样的数据结构:

[{ :2007-08-05 [ { :meat-weight-gain 100} {:meat-weight-loss 80} {:meat-balance 20}]}, 
 { :2007-08-06 [ { :meat-weight-gain 10} {:meat-weight-loss 60} {:meat-balance -30}]},
 { :2007-08-07 [ { :meat-weight-gain 40} {:meat-weight-loss 80} {:meat-balance -70}]}
 { :2007-08-08 [ { :meat-weight-gain 100} {:meat-weight-loss 0} {:meat-balance 30}]}]

我如何遍历它并返回肉类余额为负数的数据周期?示例数据将是这样的:

[ {:end-period-balance -70, :period-start 2007-08-06, :period-end 2007-08-07 } ]

除此之外,我可以改进我的数据结构还是已经可以了?如果是,如何?非常感谢。

【问题讨论】:

    标签: vector clojure


    【解决方案1】:

    我建议您将数据形状更改为元组列表,每个元组都包含日期和余额数据图。就像这样:

    (def data [[:2007-08-05 { :meat-weight-gain 100 :meat-weight-loss 80 :meat-balance 20}], 
               [:2007-08-06 { :meat-weight-gain 10 :meat-weight-loss 60 :meat-balance -30}],
               [:2007-08-07 { :meat-weight-gain 40 :meat-weight-loss 80 :meat-balance -70}]
               [:2007-08-08 { :meat-weight-gain 100 :meat-weight-loss 0 :meat-balance 30}]
               [:2007-08-09 { :meat-weight-gain 19 :meat-weight-loss -20 :meat-balance -10}]])
    

    那么就很容易按体重增加/减少对时期进行分类(使用partition-by)并收集所需信息:

    user> (let [parts (partition-by #(-> % second :meat-balance neg?) data)]
            (keep #(let [[p-start _] (first %)
                         [p-end {balance :meat-balance}] (last %)]
                     (when (neg? balance)
                       {:period-start p-start
                        :period-end p-end
                        :end-period-balance balance}))
                  parts))
    
    ;;=> ({:period-start :2007-08-06, :period-end :2007-08-07, :end-period-balance -70} 
    ;;    {:period-start :2007-08-09, :period-end :2007-08-09, :end-period-balance -10})
    

    或包含日期的地图列表:

    (def data [{:date :2007-08-05 :meat-weight-gain 100 :meat-weight-loss 80 :meat-balance 20}, 
               {:date :2007-08-06 :meat-weight-gain 10 :meat-weight-loss 60 :meat-balance -30},
               {:date :2007-08-07 :meat-weight-gain 40 :meat-weight-loss 80 :meat-balance -70}
               {:date :2007-08-08 :meat-weight-gain 100 :meat-weight-loss 0 :meat-balance 30}
               {:date :2007-08-09 :meat-weight-gain 100 :meat-weight-loss 0 :meat-balance -10}])
    
    user> (let [parts (partition-by #(-> % :meat-balance neg?) data)]
            (keep #(let [{p-start :date} (first %)
                         {p-end :date balance :meat-balance} (last %)]
                     (when (neg? balance)
                       {:period-start p-start
                        :period-end p-end
                        :end-period-balance balance}))
                  parts))
    
    ;;=> ({:period-start :2007-08-06, :period-end :2007-08-07, :end-period-balance -70} 
    ;;    {:period-start :2007-08-09, :period-end :2007-08-09, :end-period-balance -10})
    

    更新

    如果您确实需要您的初始数据格式,那么您可以使用相同的方法,只需重新定义值检索部分:

    user> (defn meat-balance [rec]
            (some :meat-balance (-> rec first second)))
    
    user> (let [parts (partition-by #(-> % meat-balance neg?) data)]
            (keep #(let [p-start (-> % first ffirst)
                         p-end (-> % last ffirst)
                         balance (-> % first meat-balance)]
                     (when (neg? balance)
                       {:period-start p-start
                        :period-end p-end
                        :end-period-balance balance}))
                  parts))
    ;;=> ({:period-start :2007-08-06, :period-end :2007-08-07, :end-period-balance -30})
    

    【讨论】:

    • 你的回答很棒,但根据我目前拥有的数据,我真的需要它
    【解决方案2】:

    更改数据格式:

    • 将每个日期的数据向量合并到一个地图中 .
    • 将整个事物制作成一张地图,以日期关键字为关键字。
    • 丢失 :meat-weight-balance 数据 - 这是多余的。

    (前两个更改遵循@leetwinski's advice

    我们得到...

    (def data
      {:2007-08-05 {:meat-weight-gain 100, :meat-weight-loss 80},
       :2007-08-06 {:meat-weight-gain 10, :meat-weight-loss 60},
       :2007-08-07 {:meat-weight-gain 40, :meat-weight-loss 80},
       :2007-08-08 {:meat-weight-gain 100, :meat-weight-loss 0}})
    

    这些条目恰好按日期顺序排列,因为它是一张小地图。如果我们想确保日期顺序,我们最好有一个排序的地图:

    (def sorted-data (into (sorted-map) data))
    

    这看起来并没有什么不同,但总是会按关键顺序显示数据,这是 - 谢天谢地 - 日期顺序。

    这似乎是一个很长的路要走,以使记录在向量中的原始顺序,但向量有未使用的日期关键字顺序穿过它:Don't R 重复Y我们自己。

    让我们计算一下每日余额:

    (def balances
      (map-vals #(- (:meat-weight-gain %)  (:meat-weight-loss %)) sorted-data))
    
    balances
    => {:2007-08-05 20, :2007-08-06 -50, :2007-08-07 -40, :2007-08-08 100}
    

    ...其中map-vals 函数是mapmapv 的类似物,适用于地图的值:

    (defn map-vals [f m]
      (into (empty m) (map (fn [[k v]] [k (f v)])) m))
    

    请注意,它返回的地图种类与给定的地图种类相同,在本例中是已排序的地图。

    我们想知道在哪些时期出现了净体重减轻。目前尚不清楚这意味着什么。让我们看看从一开始的净重增加:

    (reductions (fn [[_ av] [k v]] [k (+ av v)]) balances)
    => ([:2007-08-05 20] [:2007-08-06 -30] [:2007-08-07 -70] [:2007-08-08 30])
    

    或者我们可以将序列划分为获得和损失部分:

    (partition-by (fn [[_ v]] (neg? v)) balances)
    => (([:2007-08-05 20]) ([:2007-08-06 -50] [:2007-08-07 -40]) ([:2007-08-08 100]))
    

    我们需要partition-by 的一个变体,它通过判别函数的值作为其子序列的键,就像group-by 所做的那样。然后你就知道什么是获利范围,什么是亏损范围。一个便宜又开朗的版本是......

    (defn group-partition-by [f coll]
      (let [parts (partition-by f coll)]
        (map #(-> % first f (list %)) parts)))
    

    然后

    (group-partition-by (fn [[_ v]] (neg? v)) balances)
    => ((false ([:2007-08-05 20]))
        (true ([:2007-08-06 -50] [:2007-08-07 -40]))
        (false ([:2007-08-08 100])))
    

    您可能希望将此数据减少为从日期范围到总余额的(排序的)映射。


    转化

    我们如何从givendata?我们可以通过如下方式直接到达sorted-data

    (def sorted-data
      (->> given
           (into (sorted-map))
           (map-vals (comp #(into {} %) #(remove :meat-balance %)))))
    
    sorted-data
    =>
    {:2007-08-05 {:meat-weight-gain 100, :meat-weight-loss 80},
     :2007-08-06 {:meat-weight-gain 10, :meat-weight-loss 60},
     :2007-08-07 {:meat-weight-gain 40, :meat-weight-loss 80},
     :2007-08-08 {:meat-weight-gain 100, :meat-weight-loss 0}}
    

    展示次数

    • 你必须彻底了解sequence library
    • 地图的相应设施不在地表上。到达 与transducers 交手会有所帮助 - 不确定有多少。

    注意

    您最好使用欧洲日期,而不是美国日期,否则您将需要更聪明的keyfn 来按日期顺序获取记录。我更喜欢 clj-time local-dates 而不是关键字作为键

    • 如果代码跨越大西洋;
    • 以便您可以运行有效性检查,例如您有记录 每天。

    【讨论】:

      【解决方案3】:

      正如上面已经说过的那样,您的数据结构不适合这样的目的。这是一个分步解决方案:

      准备数据:

      (def data
        [{ :2007-08-05 [ { :meat-weight-gain 100} {:meat-weight-loss 80} {:meat-balance 20}]}, 
         { :2007-08-06 [ { :meat-weight-gain 10} {:meat-weight-loss 60} {:meat-balance -30}]},
         { :2007-08-07 [ { :meat-weight-gain 40} {:meat-weight-loss 80} {:meat-balance -70}]}
         { :2007-08-08 [ { :meat-weight-gain 100} {:meat-weight-loss 0} {:meat-balance 30}]}])
      

      创建一个新的数据结构:

      (defn turner [stats]
        (apply merge
               {:year (-> stats keys first)}
               (-> stats vals first)))
      
      (def data2 (mapv turner data))
      
      [{:year :2007-08-05, :meat-weight-gain 100, :meat-weight-loss 80, :meat-balance 20}
       {:year :2007-08-06, :meat-weight-gain 10, :meat-weight-loss 60, :meat-balance -30}
       {:year :2007-08-07, :meat-weight-gain 40, :meat-weight-loss 80, :meat-balance -70}
       {:year :2007-08-08, :meat-weight-gain 100, :meat-weight-loss 0, :meat-balance 30}]
      

      现在您可以通过一个谓词对数据进行分组,以检查余额是否为负数:

      (partition-by #(-> % :meat-balance neg?) (sort-by :year data2))
      
      (({:year :2007-08-05, :meat-weight-gain 100, :meat-weight-loss 80, :meat-balance 20})
       ({:year :2007-08-06, :meat-weight-gain 10, :meat-weight-loss 60, :meat-balance -30}
        {:year :2007-08-07, :meat-weight-gain 40, :meat-weight-loss 80, :meat-balance -70})
       ({:year :2007-08-08, :meat-weight-gain 100, :meat-weight-loss 0, :meat-balance 30}))
      

      让它成为data3。然后,过滤该数据结构以仅获取负数:

      (filter #(-> % first :meat-balance neg?) data3)
      
      (({:year :2007-08-06, :meat-weight-gain 10, :meat-weight-loss 60, :meat-balance -30}
        {:year :2007-08-07, :meat-weight-gain 40, :meat-weight-loss 80, :meat-balance -70}))
      

      让它成为data4。现在你得到了界限:

      {:period-start (-> data4 first first :year) 
       :period-end (-> data4 first last :year) 
       :end-period-balance (-> data4 first last :meat-balance)}
      

      究竟是什么给了你

      {:period-start :2007-08-06, 
       :period-end :2007-08-07, 
       :end-period-balance -70}
      

      【讨论】:

        【解决方案4】:

        首先可以解开复杂的输入数据结构:

        (map (juxt ffirst (comp first #(keep :meat-balance %) val first)))
        ;;=> ([:2007-08-05 20] [:2007-08-06 -30] [:2007-08-07 -70] [:2007-08-08 30])
        

        ...变成[date-keyword meat-balance]的元组。

        请注意,到目前为止,我们正在保持正负肉类平衡。答案需要负运行,即连续的负肉类平衡。 partition-by 是任何类型 rungo to 函数,之后我们可以过滤以仅获取答案所需的分区组。在任何事情之前,我们需要sort,因为您的日期键最初位于地图中并且地图未排序。在排序、分区和过滤之后,我们就可以提供答案了,只需将我们的规范 [date-keyword meat-balance] 数据结构转换为所需的结构:

        (->> data
             (map (juxt ffirst (comp first #(keep :meat-balance %) val first)))
             (sort-by first)
             (partition-by #(-> % second neg?))
             (filter #(-> % first second neg?))
             (map (fn [neg-run]
                   (let [[start-date _] (first neg-run)
                         [end-date end-value] (last neg-run)]
                     {:period-start start-date
                      :period-end end-date
                      :end-period-balance end-value})))
        ;;=> [{:end-period-balance -70, :period-start 2007-08-06, :period-end 2007-08-07 }]
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2020-04-16
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多