在 clojure 中懒惰地合并(分组)巨大的序列
Merge (group-by) huge sequences lazily in clojure
示例:
我们有两个通过读取 csv 创建的时间序列惰性地图序列。这
两个惰性序列在不同的日期开始:
INPUT
lazy-seq1
({:date "20110515" :val1 123}
{:date "20110516" :val1 143}
{:date "20110517" :val1 1153} ...)
lazy-seq2
({:date "20110517" :val2 151}
{:date "20110518" :val2 1330} ...)
EXPECTED OUTPUT
lazy-seq3
({:date "20110515" :vals {:val1 123}}
{:date "20110516" :vals {:val1 143}}
{:date "20110517" :vals {:val1 1153 :val2 151}}
{:date "20110518" :vals {:val1 ... :val2 1330}}
...))
准确地说,:date 的类型不是字符串,而是 clj-time 强制转换的 Jodatime
并且 :date 为每个序列排序。
首选将使用group-by函数,但我猜这不能创建lazy-seq。我相信 group-by 需要热切的评估。
第二个选择是使用 partition-by 函数,但我无法将其应用于我的 INPUTS,因为我缺乏闭包技巧。
输入序列非常大(每个序列约 1GB),我想一次计算许多(约 100 个)序列。
所以,我想要延迟评估以避免内存不足错误。
如果您的项目按日期排序,您可以轻松地对它们进行惰性合并(就像在合并排序算法中一样):
(defn merge-lazy [seq1 seq2]
(cond (empty? seq1) seq2
(empty? seq2) seq1
(< (Integer/parseInt (:date (first seq1)))
(Integer/parseInt (:date (first seq2)))) (cons (first seq1)
(lazy-seq (merge-lazy (rest seq1) seq2)))
:else (cons (first seq2) (lazy-seq (merge-lazy seq1 (rest seq2))))))
它将产生一个按日期排序的惰性序列:
user> (def seq1
'({:date "20110515" :val1 123}
{:date "20110516" :val1 143}
{:date "20110517" :val1 1153}))
#'user/seq1
user> (def seq2 '({:date "20110517" :val2 151}
{:date "20110518" :val2 1330}))
user> (merge-lazy seq1 seq2)
({:date "20110515", :val1 123} {:date "20110516", :val1 143}
{:date "20110517", :val2 151} {:date "20110517", :val1 1153}
{:date "20110518", :val2 1330})
然后您可以按日期对生成的惰性序列进行分区(这也会产生一个惰性序列):
user> (partition-by :date (merge-lazy seq1 seq2))
(({:date "20110515", :val1 123})
({:date "20110516", :val1 143})
({:date "20110517", :val2 151} {:date "20110517", :val1 1153})
({:date "20110518", :val2 1330}))
所以你接下来要做的就是用 map
处理每个组
如果你有更多的输入序列,你可以使用相同的策略,只需重写 merge-lazy
with variable args (or just reduce
with merge-lazy
: (reduce merge-lazy sequences)
this也会产生一个惰性序列的合并)
示例:
我们有两个通过读取 csv 创建的时间序列惰性地图序列。这 两个惰性序列在不同的日期开始:
INPUT
lazy-seq1
({:date "20110515" :val1 123}
{:date "20110516" :val1 143}
{:date "20110517" :val1 1153} ...)
lazy-seq2
({:date "20110517" :val2 151}
{:date "20110518" :val2 1330} ...)
EXPECTED OUTPUT
lazy-seq3
({:date "20110515" :vals {:val1 123}}
{:date "20110516" :vals {:val1 143}}
{:date "20110517" :vals {:val1 1153 :val2 151}}
{:date "20110518" :vals {:val1 ... :val2 1330}}
...))
准确地说,:date 的类型不是字符串,而是 clj-time 强制转换的 Jodatime 并且 :date 为每个序列排序。
首选将使用group-by函数,但我猜这不能创建lazy-seq。我相信 group-by 需要热切的评估。
第二个选择是使用 partition-by 函数,但我无法将其应用于我的 INPUTS,因为我缺乏闭包技巧。
输入序列非常大(每个序列约 1GB),我想一次计算许多(约 100 个)序列。 所以,我想要延迟评估以避免内存不足错误。
如果您的项目按日期排序,您可以轻松地对它们进行惰性合并(就像在合并排序算法中一样):
(defn merge-lazy [seq1 seq2]
(cond (empty? seq1) seq2
(empty? seq2) seq1
(< (Integer/parseInt (:date (first seq1)))
(Integer/parseInt (:date (first seq2)))) (cons (first seq1)
(lazy-seq (merge-lazy (rest seq1) seq2)))
:else (cons (first seq2) (lazy-seq (merge-lazy seq1 (rest seq2))))))
它将产生一个按日期排序的惰性序列:
user> (def seq1
'({:date "20110515" :val1 123}
{:date "20110516" :val1 143}
{:date "20110517" :val1 1153}))
#'user/seq1
user> (def seq2 '({:date "20110517" :val2 151}
{:date "20110518" :val2 1330}))
user> (merge-lazy seq1 seq2)
({:date "20110515", :val1 123} {:date "20110516", :val1 143}
{:date "20110517", :val2 151} {:date "20110517", :val1 1153}
{:date "20110518", :val2 1330})
然后您可以按日期对生成的惰性序列进行分区(这也会产生一个惰性序列):
user> (partition-by :date (merge-lazy seq1 seq2))
(({:date "20110515", :val1 123})
({:date "20110516", :val1 143})
({:date "20110517", :val2 151} {:date "20110517", :val1 1153})
({:date "20110518", :val2 1330}))
所以你接下来要做的就是用 map
如果你有更多的输入序列,你可以使用相同的策略,只需重写 merge-lazy
with variable args (or just reduce
with merge-lazy
: (reduce merge-lazy sequences)
this也会产生一个惰性序列的合并)