在Clojure中,如何将排序的整数数组分成连续的分区?

huangapple go评论61阅读模式
英文:

In clojure how to partition an array of sorted integers into contiguous partitions?

问题

以下是翻译好的部分:

"I'd like to partition an array of sorted integers into contiguous partitions."

"我想将一个已排序的整数数组分成连续的分区。"

"The following ruby:"

"下面的 Ruby 代码:"

"Outputs:"

"输出:"

"How can I do this in clojure?"

"我怎样在 Clojure 中实现这个?"

"I tried using partition-by, but AFAIK it only takes one argument."

"我尝试使用 partition-by,但据我所知它只接受一个参数。"

英文:

I'd like to partition an array of sorted integers into contiguous partitions.

The following ruby:

[1,2,3,8,9,10,99].slice_when { |x, y| y > x + 1 }.to_a

Outputs:

[[1, 2, 3], [8, 9, 10], [99]]

How can I do this in clojure?

I tried using partition-by, but AFAIK it only takes one argument.

答案1

得分: 4

你必须在某个地方保存上一个元素。在这种情况下,你可以这样做:

(defn slice [coll]
  (let [prev (atom (first coll))]
    (->> coll
         (partition-by #(> (inc @prev) (reset! prev %))))))

(slice [1 2 3 8 9 10 99])
=> ((1 2 3) (8 9 10) (99))

如果你还想提供这个函数,你将需要编写一个基于reduce的解决方案:

(defn slice-when [pred coll]
  (->> coll
       (reduce (fn [acc current]
                 (if-let [previous (peek (peek acc))]
                   (if (pred previous current)
                     (conj acc [current])
                     (conj (pop acc) (conj (peek acc) current)))
                   (conj acc [current])))
               [])))

示例:

(slice-when (fn [x y] (> y (inc x))) 
            [1 2 3 8 9 10 99])
=> [[1 2 3] [8 9 10] [99]]
英文:

You have to save somewhere that previous element. In this case, you can do:

(defn slice [coll]
  (let [prev (atom (first coll))]
    (->> coll
         (partition-by #(< (inc @prev) (reset! prev %))))))

(slice [1 2 3 8 9 10 99])
=> ((1 2 3) (8 9 10) (99))

If you also want to provide that function, you will have to write some reduce-based solution:

(defn slice-when [pred coll]
  (->> coll
       (reduce (fn [acc current]
                 (if-let [previous (peek (peek acc))]
                   (if (pred previous current)
                     (conj acc [current])
                     (conj (pop acc) (conj (peek acc) current)))
                   (conj acc [current])))
               [])))

Example:

(slice-when (fn [x y] (> y (inc x))) 
            [1 2 3 8 9 10 99])
=> [[1 2 3] [8 9 10] [99]]

答案2

得分: 1

你也可以标记所有的分割点,然后只是根据它们进行分区。

(defn split-when [pred data]  
  (when-let [[x & xs] (seq data)]
    (->> (mapcat (fn [a b] (if (pred a b) [::split b] [b])) 
                 data xs)
         (cons x)
         (partition-by #{::split})
         (take-nth 2))))

用户> (split-when (complement (comp #{-1} -)) [1 5 6 7 9 10 22])
;;=> ((1) (5 6 7) (9 10) (22))

或者,你可以收集应该分割的所有索引,并传递给subvec

(defn split-when [pred data]   
  (when (seq data)
    (let [splits (keep-indexed (fn [i [a b]] (when (pred a b) (inc i)))
                               (partition 2 1 data))
          indices `[0 ~@splits ~(count data)]]
      (map (partial subvec (vec data)) indices (rest indices)))))

用户> (split-when (complement (comp #{-1} -)) [1 5 6 7 9 10 22])
;;=> (1 [5 6 7] [9 10] [22])

英文:

you could also mark all the split points, and then just partition with them

(defn split-when [pred data]  
  (when-let [[x & xs] (seq data)]
    (->> (mapcat (fn [a b] (if (pred a b) [::split b] [b])) 
                 data xs)
         (cons x)
         (partition-by #{::split})
         (take-nth 2))))
user> (split-when (complement (comp #{-1} -)) [1 5 6 7 9 10 22])
;;=> ((1) (5 6 7) (9 10) (22))

otherwise you can collect all the indices where you should split and pass over to subvec:

 (defn split-when [pred data]   
   (when (seq data)
     (let [splits (keep-indexed (fn [i [a b]] (when (pred a b) (inc i)))
                                (partition 2 1 data))
           indices `[0 ~@splits ~(count data)]]
       (map (partial subvec (vec data)) indices (rest indices)))))
user> (split-when (complement (comp #{-1} -)) [1 5 6 7 9 10 22])
;;=> ([1] [5 6 7] [9 10] [22])

答案3

得分: 1

以下是您提供的内容的中文翻译部分:

我编写了一个通用版本,如下所示。

它将数据向量分割为所有可能的点,将子向量传递给类似于以下的谓词函数:

(fn [data1 data2] ...)

它跟踪所有谓词返回 true 的索引,然后使用 reducesubvec 来累积结果。我包含了一个单元测试。

(ns tst.demo.core
  (:use demo.core tupelo.core tupelo.test))

; 我们使用 `split-at` 来对集合进行分区。对于像 [0 1 2] 这样的集合,
; 提供像 {:data1 [] :data2 [0 1 2]} 或 {:data1 [0 1 2] :data2 []} 这样的退化分割是没有意义的。
; 因此,我们需要在范围 [1..(N-1)] 内找到分割点
(verify
  (let [data   [0 1 2]
        N      (count data)
        result (forv [i (thru N)]
                 (let [[data1 data2] (split-at i data)]
                   (vals->map i data1 data2)))]

    (is= result
      [{:i 0 :data1 [] :data2 [0 1 2]}
       {:i 1 :data1 [0] :data2 [1 2]}
       {:i 2 :data1 [0 1] :data2 [2]}
       {:i 3 :data1 [0 1 2] :data2 []}])))

因此,我们避免了 data1 或 data2 中的一个为空向量的退化情况。

(defn partition-when
  [pred coll]
  (let [data       (vec coll)
        N          (count data)
        N-1        (dec N)
        ; 我们使用 `split-at` 来对集合进行分区。对于像 [0 1 2] 这样的集合,
        ; 提供像 {:data1 [] :data2 [0 1 2]} 或 {:data1 [0 1 2] :data2 []} 这样的退化分割是没有意义的。
        ; 因此,我们需要在范围 [1..(N-1)] 内找到分割点
        split-idxs (loop [i    1
                          idxs []]
                     (if-not (< i N-1)
                       idxs
                       (let [[data1 data2] (split-at i data)
                             split?      (pred data1 data2)
                             result-next (if split?
                                           (append idxs i)
                                           idxs)
                             i-next      (inc i)]
                         (recur i-next result-next))))
        ; 由于我们使用 `subvec`,我们必须在向量的开始/结束添加“默认”值
        split-idxs (glue [0] split-idxs [N])
        idx-pairs  (partition 2 1 split-idxs)
        subvecs    (reduce (fn [cum [start end]]
                             (let [next-part (subvec data start end)
                                   next-cum  (append cum next-part)]
                               next-cum))
                     []
                     idx-pairs)]
    subvecs))

以及单元测试。请注意,谓词函数必须负责从 data1data2 中获取单个元素。

(verify
  (let [split-fn (fn [data1 data2]
                   (let [x (last data1)
                         y (first data2)]
                     (< (inc x) y)))]
    (is= [[1 2 3] [8 9 10] [99]]
      (partition-when split-fn
        [1 2 3 8 9 10 99]))))

这个函数现在是 Tupelo 库的一部分。

英文:

I wrote a general version shown below.

It splits the data vector at all possible points, presenting the sub-vectors to a predicate function that looks like:

(fn [data1 data2] ...)

It keeps track of all the indexes where the pred returns true, then uses reduce and subvec to accumulate the result. I've included a unit test.

(ns tst.demo.core
  (:use demo.core tupelo.core tupelo.test))

; We use `split-at` to partition the coll. Given a coll like [0 1 2], it makes
; no sense to give degenerate splits like {:data1 [] :data2 [0 1 2]}
; or {:data1 [0 1 2] :data2 []}. So, we need the split point in the range [1..(N-1)]
(verify
  (let [data   [0 1 2]
        N      (count data)
        result (forv [i (thru N)]
                 (let [[data1 data2] (split-at i data)]
                   (vals-&gt;map i data1 data2)))]
    (is= result
      [{:i 0 :data1 [] :data2 [0 1 2]}
       {:i 1 :data1 [0] :data2 [1 2]}
       {:i 2 :data1 [0 1] :data2 [2]}
       {:i 3 :data1 [0 1 2] :data2 []}])))

So, we avoid the degenerate case where either data1 or data2 is the empty vector.

(defn partition-when
  [pred coll]
  (let [data       (vec coll)
        N          (count data)
        N-1        (dec N)
        ; We use `split-at` to partition the coll. Given a coll like [0 1 2], it makes
        ; no sense to give degenerate splits like {:data1 [] :data2 [0 1 2]}
        ; or {:data1 [0 1 2] :data2 []}. So, we need the split point in the range [1..(N-1)]
        split-idxs (loop [i    1
                          idxs []]
                     (if-not (&lt;= i N-1)
                       idxs
                       (let [[data1 data2] (split-at i data)
                             split?      (pred data1 data2)
                             result-next (if split?
                                           (append idxs i)
                                           idxs)
                             i-next      (inc i)]
                         (recur i-next result-next))))
        ; Since we are using `subvec`, we must add in the &quot;default&quot; values at the
        ; start/end of the vector
        split-idxs (glue [0] split-idxs [N])
        idx-pairs  (partition 2 1 split-idxs)
        subvecs    (reduce (fn [cum [start end]]
                             (let [next-part (subvec data start end)
                                   next-cum  (append cum next-part)]
                               next-cum))
                     []
                     idx-pairs)]
    subvecs))

and the unit test. Notice the predicate function must take responsibility for pulling individual elements out of data1 and data2

(verify
  (let [split-fn (fn [data1 data2]
                   (let [x (last data1)
                         y (first data2)]
                     (&lt; (inc x) y)))]
    (is= [[1 2 3] [8 9 10] [99]]
      (partition-when split-fn
        [1 2 3 8 9 10 99])))
  )

This function is now part of the Tupelo library.

答案4

得分: 0

我觉得这个解决方案非常简洁:

(defn partition-contiguous [numbers]
  (->> numbers
       (partition-by identity)
       (map-indexed cons)
       (partition-by (fn [[x y]] (- x y)))
       (map #(mapcat rest %))))

(partition-contiguous [1 2 3 3 8 9 9 10 10 99])
;; => ((1 2 3 3) (8 9 9 10 10) (99))
英文:

I find this solution pretty concise:

(defn partition-contiguous [numbers]
  (-&gt;&gt; numbers
       (partition-by identity)
       (map-indexed cons)
       (partition-by (fn [[x y]] (- x y)))
       (map #(mapcat rest %))))

(partition-contiguous [1 2 3 3 8 9 9 10 10 99])
;; =&gt; ((1 2 3 3) (8 9 9 10 10) (99))

答案5

得分: 0

The library dev.weavejester/medley ("一个小而有用的函数集合,大多数函数在 clojure.core 命名空间中都不会显得不合适") 自 1.7.0 版本以来包含了 partition-between 函数。该函数可用于将已排序的集合划分为连续的分区:

(require '[medley.core :refer [partition-between]])

(partition-between (fn [x y] (> y (inc x))) [1 2 3 8 9 10 99])
;; => ((1 2 3) (8 9 10) (99))
英文:

The library dev.weavejester/medley ("A small collection of useful, mostly pure functions that might not look out of place in the clojure.core namespace") contains partition-between since 1.7.0. That function can be used to partition a sorted collection into contiguous partitions:

(require &#39;[medley.core :refer [partition-between]])

(partition-between (fn [x y] (&gt; y (inc x))) [1 2 3 8 9 10 99])
;; =&gt; ((1 2 3) (8 9 10) (99))

huangapple
  • 本文由 发表于 2023年3月3日 22:03:40
  • 转载请务必保留本文链接:https://go.coder-hub.com/75628071.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定