Algorithm 一个区间和一组区间之间的差异?

Algorithm 一个区间和一组区间之间的差异?,algorithm,go,set,intervals,set-difference,Algorithm,Go,Set,Intervals,Set Difference,我有一组不重叠的,不相邻的区间,例如[10,15},{30,35},{20,25}]。它们没有分类,但如果需要,我可以对它们进行分类 现在我得到了一些新的区间,例如{5,32},我想生成一组新的区间来描述差异:这个新区间覆盖的范围不在这个区间集中。在这个例子中,答案是:[{5,9},{16,19},{26,29}] 计算这个的快速算法是什么?请注意,集合中通常有1个,有时有2个,很少有3个或更多的项目,因此我想针对这种情况进行优化 对于上下文,下面是从开始+结束数据的输入流开始创建集合的代码,我

我有一组不重叠的,不相邻的区间,例如[10,15},{30,35},{20,25}]。它们没有分类,但如果需要,我可以对它们进行分类

现在我得到了一些新的区间,例如{5,32},我想生成一组新的区间来描述差异:这个新区间覆盖的范围不在这个区间集中。在这个例子中,答案是:[{5,9},{16,19},{26,29}]

计算这个的快速算法是什么?请注意,集合中通常有1个,有时有2个,很少有3个或更多的项目,因此我想针对这种情况进行优化

对于上下文,下面是从开始+结束数据的输入流开始创建集合的代码,我在其中进行合并:

type Interval struct {
    start int
    end   int
}

func (i *Interval) OverlapsOrAdjacent(j Interval) bool {
    return i.end+1 >= j.start && j.end+1 >= i.start
}

func (i *Interval) Merge(j Interval) bool {
    if !i.OverlapsOrAdjacent(j) {
        return false
    }
    if j.start < i.start {
        i.start = j.start
    }
    if j.end > i.end {
        i.end = j.end
    }
    return true
}

type Intervals []Interval

func (ivs Intervals) Len() int           { return len(ivs) }
func (ivs Intervals) Swap(i, j int)      { ivs[i], ivs[j] = ivs[j], ivs[i] }
func (ivs Intervals) Less(i, j int) bool { return ivs[i].start < ivs[j].start }

func (ivs Intervals) Merge(iv Interval) Intervals {
    ivs = append(ivs, iv)
    merged := make(Intervals, 0, len(ivs))
    for _, iv := range ivs {
        for i := 0; i < len(merged); {
            if iv.Merge(merged[i]) {
                merged = append(merged[:i], merged[i+1:]...)
            } else {
                i++
            }
        }
        merged = append(merged, iv)
    }
    return merged
}

func (ivs Intervals) MergeUsingSort(iv Interval) Intervals {
    ivs = append(ivs, iv)
    sort.Sort(ivs)
    merged := make(Intervals, 0, len(ivs))
    merged = append(merged, ivs[0])
    for i := 1; i < len(ivs); i++ {
        last := len(merged) - 1
        if !merged[last].Merge(ivs[i]) {
            merged = append(merged, ivs[i])
        }
    }
    return merged
}

func (ivs Intervals) Difference(iv Interval) Intervals {
    // ???
    return ivs
}

func main() {
    var ivs Intervals
    for _, input := range inputsFromSomewhere { // in reality, I don't have all these inputs at once, they come in one at a time
        iv := Interval{input.start, input.end}
        diffs := ivs.Difference(iv) // not yet implemented...
        // do something with diffs
        ivs = ivs.Merge(iv)
    }
}
类型区间结构{
起始整数
结束整型
}
func(i*区间)重叠或相邻(j区间)布尔{
返回i.end+1>=j.start&&j.end+1>=i.start
}
func(i*区间)合并(j区间)bool{
如果!i.i(j){
返回错误
}
如果j.starti.end{
i、 end=j.end
}
返回真值
}
键入间隔[]间隔
func(ivs区间)Len()int{return Len(ivs)}
func(ivs区间)Swap(i,j int){ivs[i],ivs[j]=ivs[j],ivs[i]}
func(ivs区间)小于(i,j int)bool{return ivs[i]。start

我发现上面的interval.Merge()比MergeUsingSort()快2倍,所以我想知道是否还有一种简单的非排序方式来回答我的问题。

为了回答我自己的问题,下面是我对Difference()的实现,它(在我的输入数据上)比JimB要求排序的建议更快

func (i *Interval) Overlaps(j Interval) bool {
    return i.End >= j.Start && j.End >= i.Start
}

func (i *Interval) Difference(j Interval) (left *Interval, right *Interval, overlapped bool) {
    if !i.Overlaps(j) {
        return
    }

    overlapped = true
    if j.Start < i.Start {
        left = &Interval{j.Start, i.Start - 1}
    }
    if j.End > i.End {
        right = &Interval{i.End + 1, j.End}
    }
    return
}

func (ivs Intervals) Difference(iv Interval) (diffs Intervals) {
    diffs = append(diffs, iv)
    for _, prior := range ivs {
        for i := 0; i < len(diffs); {
            if left, right, overlapped := prior.Difference(diffs[i]); overlapped {
                if len(diffs) == 1 {
                    diffs = nil
                } else {
                    diffs = append(diffs[:i], diffs[i+1:]...)
                }

                if left != nil {
                    diffs = append(diffs, *left)
                }
                if right != nil {
                    diffs = append(diffs, *right)
                }
            } else {
                i++
            }
        }
        if len(diffs) == 0 {
            break
        }
    }
    return
}
func(i*区间)与(j区间)bool重叠{
返回i.End>=j.Start和&j.End>=i.Start
}
func(i*区间)差(j区间)(左*区间、右*区间、重叠区间){
如果!i.重叠(j){
返回
}
重叠=真
如果j.开始i.End{
右=&区间{i.End+1,j.End}
}
返回
}
func(ivs间期)差值(iv间期)(差值间期){
差异=附加(差异,iv)
对于u,前:=范围ivs{
对于i:=0;i

它对我尝试过的数据有效,尽管我有点担心我可能错过了一个边缘案例,在那里它得到了错误的答案。

问答代码不完整,无法编译。没有基准。快速浏览一下代码,它可能效率低下

interval.go
interval\u test.go
的可用代码来自

让我们从为间隔差示例编写基准开始

package minfys

import (
    "fmt"
    "testing"
)

// Example
var (
    xA = Intervals{{10, 15}, {30, 35}, {20, 25}}
    xB = Interval{5, 32}
    xD = Intervals{{5, 9}, {16, 19}, {26, 29}}
    xR = Intervals{}
)

func BenchmarkExample(b *testing.B) {
    b.ReportAllocs()
    a := make(Intervals, len(xA))
    b.ResetTimer()
    for i := 0; i < b.N; i++ {
        copy(a, xA)
        xR = a.Difference(xB)
    }
    b.StopTimer()
    if fmt.Sprint(xD) != fmt.Sprint(xR) {
        b.Fatal(xD, xR)
    }
}
sbs编写了一个差分方法

package minfys

func (a Intervals) Difference(b Interval) Intervals {
    // If A and B are sets, then the relative complement of A in B
    // is the set of elements in B but not in A.
    // The relative complement of A in B is denoted B ∖  A:
    //     B \ A = {x ∈ A | x ∉ B}
    //     B \ A = B ∩ A'
    //
    // For example. d = a\b,
    //     a: [{10, 15}, {30, 35}, {20, 25}]
    //     b: {5,32}
    //     d: [{5,9}, {16,19}, {26,29}]
    // The elements of set a are non-overlapping, non-adjacent,
    // and unsorted intervals.

    if len(a) <= 0 {
        return Intervals{b}
    }

    d := make(Intervals, 0, 3)
    for ; len(a) > 0; a = a[1:] {
        for i := 1; i < len(a); i++ {
            if a[i].Start < a[0].Start {
                a[i], a[0] = a[0], a[i]
            }
        }

        if b.Start < a[0].Start {
            if b.End < a[0].Start {
                d = append(d, b)
                break
            }
            d = append(d, Interval{b.Start, a[0].Start - 1})
            b.Start = a[0].Start
        }
        if b.End <= a[0].End {
            break
        }
        if b.Start <= a[0].End {
            b.Start = a[0].End + 1
        }
        if len(a) == 1 {
            if b.Start <= a[0].End {
                b.Start = a[0].End + 1
            }
            d = append(d, b)
            break
        }
    }
    return d
}
BenchmarkExample-4     20000000     62.4 ns/op    48 B/op      1 allocs/op
// Interval struct is used to describe something with a start and end. End must
// be greater than start.
type Interval struct {
    Start int64
    End   int64
}

// Overlaps returns true if this interval overlaps with the supplied one.
func (i *Interval) Overlaps(j Interval) bool {
    // https://nedbatchelder.com/blog/201310/range_overlap_in_two_compares.html
    return i.End >= j.Start && j.End >= i.Start
}

// Intervals type is a slice of Interval.
type Intervals []Interval

// Difference returns any portions of iv that do not overlap with any of our
// intervals. Assumes that all of our intervals have been Merge()d in.
func (ivs Intervals) Difference(iv Interval) (diffs Intervals) {
    diffs = append(diffs, iv)
    for _, prior := range ivs {
        for i := 0; i < len(diffs); {
            if left, right, overlapped := prior.Difference(diffs[i]); overlapped {
                if len(diffs) == 1 {
                    diffs = nil
                } else {
                    diffs = append(diffs[:i], diffs[i+1:]...)
                }

                if left != nil {
                    diffs = append(diffs, *left)
                }
                if right != nil {
                    diffs = append(diffs, *right)
                }
            } else {
                i++
            }
        }
        if len(diffs) == 0 {
            break
        }
    }

    return
}
BenchmarkExample-4      5000000    365 ns/op     128 B/op      4 allocs/op
彼得索的差分法要快得多

old.txt (sbs) versus new.txt (peterSO):

benchmark              old ns/op     new ns/op     delta
BenchmarkExample-4     365           62.4          -82.90%

benchmark              old allocs     new allocs   delta
BenchmarkExample-4     4              1            -75.00%

benchmark              old bytes     new bytes     delta
BenchmarkExample-4     128           48            -62.50%
这只是一个开始。可能还有其他可以改进的地方

interval\u test.go
中出现一些错误<代码>ShouldBeNil
用于指针<代码>应为空
用于收集<代码>应相似不处理集合相等(包含相同元素的两个集合是同一集合)。更改
应类似于
顺序,以匹配依赖于实施的顺序

$ go test
..........................................................................................................................x......................................................x................x
Failures:

  * interval_test.go 
  Line 247:
  Expected: nil
  Actual:   '[]'

  * interval_test.go 
  Line 375:
  Expected: 'minfys.Intervals{minfys.Interval{Start:5, End:6}, minfys.Interval{Start:31, End:32}, minfys.Interval{Start:11, End:14}, minfys.Interval{Start:19, End:19}}'
  Actual:   'minfys.Intervals{minfys.Interval{Start:5, End:6}, minfys.Interval{Start:11, End:14}, minfys.Interval{Start:19, End:19}, minfys.Interval{Start:31, End:32}}'
  (Should resemble)!

  * interval_test.go 
  Line 413:
  Expected: 'minfys.Intervals{minfys.Interval{Start:7, End:10}, minfys.Interval{Start:1, End:3}, minfys.Interval{Start:15, End:17}}'
  Actual:   'minfys.Intervals{minfys.Interval{Start:1, End:3}, minfys.Interval{Start:7, End:10}, minfys.Interval{Start:15, End:17}}'
  (Should resemble)!


195 total assertions

...
198 total assertions

--- FAIL: TestIntervals (0.04s)
FAIL


我[]确认它比我的解决方案快。如果你只是 使用Difference()测量所需的墙时间(将a置于:= time.Now()在interval_test.go中的最后一个Difference()调用之前, 和一个时间。从它之后(之前)开始,并将这些时间加起来 (在我的机器上)它似乎没有什么不同 我的解决方案需要约31毫秒,而你的解决方案需要约29毫秒)

根据要求,修改了间隔试验go,以测量壁时间:

$ diff -a -u ../interval_test.go walltime_test.go
--- ../interval_test.go 2017-04-29 20:23:29.365344008 -0400
+++ walltime_test.go    2017-04-30 13:39:29.000000000 -0400
@@ -24,6 +24,7 @@
    "math/rand"
    "testing"
    "time"
+   "fmt"
 )

 func TestIntervals(t *testing.T) {
@@ -459,16 +460,20 @@

        var ivs Intervals
        errors := 0
+       var diffTime time.Duration
        t := time.Now()
        for i, input := range inputs {
            iv := NewInterval(int64(input), int64(readSize))
+           before := time.Now()
            newIvs := ivs.Difference(iv)
+           diffTime += time.Since(before)
            if (len(newIvs) == 1) != exepectedNew[i] {
                errors++
            }
            ivs = ivs.Merge(iv)
        }
-       // fmt.Printf("\ntook %s\n", time.Since(t))
+       fmt.Printf("took %s\n", time.Since(t))
+       fmt.Printf("\n  Difference took %s\n", diffTime)
        So(errors, ShouldEqual, 0)
        So(len(ivs), ShouldEqual, 1)
        So(time.Since(t).Seconds(), ShouldBeLessThan, 1) // 42ms on my machine
$ 
interval_test.go
基准输入大小和频率

size    frequency
0       1
1       94929
2       50072
3       4998
size    frequency
0       50000
1       100000
输出的大小和频率是相同的

size    frequency
0       1
1       94929
2       50072
3       4998
size    frequency
0       50000
1       100000
调整彼得索的差分法得到了这个分布

package minfys

func (a Intervals) Difference(b Interval) Intervals {
    // If A and B are sets, then the relative complement of A in B
    // is the set of elements in B but not in A.
    // The relative complement of A in B is denoted B ∖  A:
    //     B \ A = {x ∈ A | x ∉ B}
    //     B \ A = B ∩ A'
    //
    // For example. d = a\b,
    //     a: [{10, 15}, {30, 35}, {20, 25}]
    //     b: {5,32}
    //     d: [{5,9}, {16,19}, {26,29}]
    // The elements of set a are non-overlapping, non-adjacent,
    // and unsorted intervals.

    if len(a) <= 0 {
        return Intervals{b}
    }

    var d Intervals
    for ; len(a) > 0; a = a[1:] {
        for i := 1; i < len(a); i++ {
            if a[i].Start < a[0].Start {
                a[i], a[0] = a[0], a[i]
            }
        }

        if b.Start < a[0].Start {
            if b.End < a[0].Start {
                d = append(d, b)
                break
            }
            d = append(d, Interval{b.Start, a[0].Start - 1})
            b.Start = a[0].Start
        }
        if b.End <= a[0].End {
            break
        }
        if b.Start <= a[0].End {
            b.Start = a[0].End + 1
        }
        if len(a) == 1 {
            if b.Start <= a[0].End {
                b.Start = a[0].End + 1
            }
            d = append(d, b)
            break
        }
    }
    return d
}

彼得索的差分法明显快于sbs法:10.706858ms,而不是14.414488ms或-25。
$ go test -v

  Merging many intervals is fast took 26.208614ms

  Difference took 10.706858ms
$ go test -v

  Merging many intervals is fast took 30.799216ms

  Difference took 14.414488ms
old.txt (sbs) versus new.txt (peterSO):

benchmark              old ns/op     new ns/op     delta
BenchmarkExample-4     365           221           -39.45%

benchmark              old allocs     new allocs   delta
BenchmarkExample-4     4              3            -25.00%

benchmark              old bytes     new bytes     delta
BenchmarkExample-4     128           112           -12.50%