在Clojure中跨分区进行分区?

时间:2014-01-21 16:22:22

标签: clojure lazy-sequences

以下是一些值。每个都是一系列升序(或其他分组)的值。

(def input-vals [[[1 :a] [1 :b] [2 :c] [3 :d] [3 :e]]
           [[1 :f] [2 :g] [2 :h] [2 :i] [3 :j] [3 :k]]
           [[1 :l] [3 :m]]])

我可以按值对它们进行分区。

=> (map (partial partition-by first) input-vals)
   ((([1 :a] [1 :b]) ([2 :c]) ([3 :d] [3 :e])) (([1 :f]) ([2 :g] [2 :h] [2 :i]) ([3 :j] [3 :k])) (([1 :l]) ([3 :m])))

但这让我获得了3个分区序列。我想要一个分区组序列。

我想要做的是返回一个(潜在的)延迟序列的懒惰序列,这些序列是连接的相应分区。例如我想要产生这个:

((([1 :a] [1 :b] [1 :f] [1 :l]) ([2 :c] [2 :g] [2 :h] [2 :i]) ([3 :d] [3 :e] [3 :j] [3 :k] [3 :m])))

请注意,并非所有值都出现在所有序列中(第三个向量中没有2)。

这当然是我问题的简化。真实数据是来自非常大的文件的一组延迟流,因此无法实现任何内容。但我认为上述问题的解决方案是我的问题的解决方案。

随意编辑标题,我不太清楚如何表达它。

5 个答案:

答案 0 :(得分:2)

试试这个恐怖:

(defn partition-many-by [f comp-f s]
  (let [sorted-s (sort-by first comp-f s)
        first-list (first (drop-while (complement seq) sorted-s))
        match-val (f (first first-list))
        remains (filter #(not (empty? %)) 
                        (map #(drop-while (fn [ss] (= match-val (f ss))) %) 
                             sorted-s))]
    (when match-val
      (cons
        (apply concat
          (map #(take-while (fn [ss] (= match-val (f ss))) %)
               sorted-s))
        (lazy-seq (partition-many-by f comp-f remains))))))

可以改进删除双值检查(take-while和drop-while)。

使用示例:

(partition-many-by identity [[1 1 1 1 2 2 3 3 3 3] [1 1 2 2 2 2 3] [3]])

=> ((1 1 1 1 1 1) (2 2 2 2 2 2) (3 3 3 3 3 3))

答案 1 :(得分:2)

让我们感兴趣并使用无限长度的序列作为我们的输入

(def twos (iterate #(+ 2 %) 0))
(def threes (iterate #(+ 3 %) 0))
(def fives (iterate #(+ 5 %) 0))

我们需要懒洋洋地合并它们。我们要求比较器,以便我们也可以应用于其他数据类型。

(defn lazy-merge-by
 ([compfn xs ys] 
  (lazy-seq
    (cond
      (empty? xs) ys
      (empty? ys) xs
      :else (if (compfn (first xs) (first ys)) 
              (cons (first xs) (lazy-merge-by compfn (rest xs) ys))
              (cons (first ys) (lazy-merge-by compfn xs (rest ys)))))))
  ([compfn xs ys & more] 
   (apply lazy-merge-by compfn (lazy-merge-by compfn xs ys) more)))

测试

(take 15 (lazy-merge-by < twos threes fives))
;=> (0 0 0 2 3 4 5 6 6 8 9 10 10 12 12)

如果需要,我们可以(懒惰地)按值分区

(take 10 (partition-by identity (lazy-merge-by < twos threes fives)))
;=> ((0 0 0) (2) (3) (4) (5) (6 6) (8) (9) (10 10) (12 12))

现在,回到示例输入

(partition-by first (apply lazy-merge-by #(<= (first %) (first %2)) input-vals))
;=> (([1 :a] [1 :b] [1 :f] [1 :l]) ([2 :c] [2 :g] [2 :h] [2 :i]) ([3 :d] [3 :e] [3 :j] [3 :k] [3 :m]))

根据需要减去一组无关的外括号。

答案 2 :(得分:1)

我不确定我是否关注,但你可以对结果序列进行评估,例如:

(flatten (partition-by identity (first input-vals)))
  

clojure.core /平铺
  ([X])
      采用顺序事物的任何嵌套组合(列表,向量,
       等)并将其内容作为单个平面序列返回     (flatten nil)返回一个空序列。

你可以用实现吗?用于测试序列是否是惰性的函数。

答案 3 :(得分:1)

user> (def desired-result '((([1 :a] [1 :b] [1 :f] [1 :l])
                             ([2 :c] [2 :g] [2 :h] [2 :i])
                             ([3 :d] [3 :e] [3 :j] [3 :k] [3 :m]))))
#'user/desired-result

user> (def input-vals [[[1 :a] [1 :b] [2 :c] [3 :d] [3 :e]]
                       [[1 :f] [2 :g] [2 :h] [2 :i] [3 :j] [3 :k]]
                       [[1 :l] [3 :m]]])
#'user/input-vals

user> (= desired-result (vector (vals (group-by first (apply concat input-vals)))))
true

我稍微更改了输入值以纠正我认为是打字错误,如果不是错误我可以更新我的代码以适应不太规则的结构。

使用->>(thread last)宏,我们可以以更易读的形式获得等效代码:

user> (= desired-result
         (->> input-vals
           (apply concat)
           (group-by first)
           vals
           vector))
true

答案 4 :(得分:0)

(partition-by first (sort-by first (mapcat identity input-vals)))