Skip to content

Commit

Permalink
use sort.Search for insertion and subset
Browse files Browse the repository at this point in the history
Signed-off-by: Elazar Gershuni <[email protected]>
  • Loading branch information
Elazar Gershuni committed Mar 7, 2024
1 parent 4aae8c9 commit e1d68f7
Show file tree
Hide file tree
Showing 3 changed files with 25 additions and 137 deletions.
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ fmt:
lint:
@echo -- $@ --
CGO_ENABLED=0 go vet ./...
golangci-lint run --new
golangci-lint run

precommit: mod fmt lint

Expand Down
10 changes: 0 additions & 10 deletions pkg/intervals/interval.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,16 +27,6 @@ func (i *Interval) overlaps(other Interval) bool {
return other.End >= i.Start && other.Start <= i.End
}

func (i *Interval) touches(other Interval) bool {
if i.Start > other.End {
return i.Start == other.End+1
}
if other.Start > i.End {
return other.Start == i.End+1
}
return false
}

func (i *Interval) isSubset(other Interval) bool {
return other.Start <= i.Start && other.End >= i.End
}
Expand Down
150 changes: 24 additions & 126 deletions pkg/intervals/intervalset.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ package intervals

import (
"fmt"
"slices"
"sort"
)

// CanonicalIntervalSet is a canonical representation of a set of Interval objects
Expand Down Expand Up @@ -33,119 +35,22 @@ func (c *CanonicalIntervalSet) Equal(other CanonicalIntervalSet) bool {
return true
}

func (c *CanonicalIntervalSet) findIntervalLeft(interval Interval) int {
if c.IsEmpty() {
return -1
}
low := 0
high := len(c.IntervalSet)
for {
if low == high {
break
}
mid := (low + high) / 2
if c.IntervalSet[mid].End < interval.Start-1 {
if mid == len(c.IntervalSet)-1 || c.IntervalSet[mid+1].End >= interval.Start-1 {
return mid
}
low = mid + 1
} else {
high = mid
}
// AddInterval adds a new interval range to the set
func (c *CanonicalIntervalSet) AddInterval(v Interval) {
set := c.IntervalSet
left := sort.Search(len(set), func(i int) bool {
return set[i].End >= v.Start-1
})
if left < len(set) && set[left].Start <= v.End {
v.Start = min(v.Start, set[left].Start)
}
if low == len(c.IntervalSet) {
low -= 1
right := sort.Search(len(set), func(j int) bool {
return set[j].Start > v.End+1
})
if right > 0 && set[right-1].End >= v.Start {
v.End = max(v.End, set[right-1].End)
}
if c.IntervalSet[low].End >= interval.Start-1 {
return -1
}
return low
}

func (c *CanonicalIntervalSet) findIntervalRight(interval Interval) int {
if c.IsEmpty() {
return -1
}
low := 0
high := len(c.IntervalSet)
for {
if low == high {
break
}
mid := (low + high) / 2
if c.IntervalSet[mid].Start > interval.End+1 {
if mid == 0 || c.IntervalSet[mid-1].Start <= interval.End+1 {
return mid
}
high = mid
} else {
low = mid + 1
}
}
if low == len(c.IntervalSet) {
low -= 1
}
if c.IntervalSet[low].Start <= interval.End+1 {
return -1
}
return low
}

func insert(array []Interval, element Interval, i int) []Interval {
return append(array[:i], append([]Interval{element}, array[i:]...)...)
}

// AddInterval updates the current CanonicalIntervalSet with a new Interval to add
//
//gocyclo:ignore
func (c *CanonicalIntervalSet) AddInterval(intervalToAdd Interval) {
if c.IsEmpty() {
c.IntervalSet = append(c.IntervalSet, intervalToAdd)
return
}
left := c.findIntervalLeft(intervalToAdd)
right := c.findIntervalRight(intervalToAdd)

// interval_to_add has no overlapping/touching intervals between left to right
if left >= 0 && right >= 0 && right-left == 1 {
c.IntervalSet = insert(c.IntervalSet, intervalToAdd, left+1)
return
}

// interval_to_add has no overlapping/touching intervals and is smaller than first interval
if left == -1 && right == 0 {
c.IntervalSet = insert(c.IntervalSet, intervalToAdd, 0)
return
}

// interval_to_add has no overlapping/touching intervals and is greater than last interval
if right == -1 && left == len(c.IntervalSet)-1 {
c.IntervalSet = append(c.IntervalSet, intervalToAdd)
return
}

// update left/right indexes to be the first potential overlapping/touching intervals from left/right
left += 1
if right >= 0 {
right -= 1
} else {
right = len(c.IntervalSet) - 1
}
// check which of left/right is overlapping/touching interval_to_add
leftOverlaps := c.IntervalSet[left].overlaps(intervalToAdd) || c.IntervalSet[left].touches(intervalToAdd)
rightOverlaps := c.IntervalSet[right].overlaps(intervalToAdd) || c.IntervalSet[right].touches(intervalToAdd)
newIntervalStart := intervalToAdd.Start
if leftOverlaps && c.IntervalSet[left].Start < newIntervalStart {
newIntervalStart = c.IntervalSet[left].Start
}
newIntervalEnd := intervalToAdd.End
if rightOverlaps && c.IntervalSet[right].End > newIntervalEnd {
newIntervalEnd = c.IntervalSet[right].End
}
newInterval := Interval{Start: newIntervalStart, End: newIntervalEnd}
tmp := c.IntervalSet[right+1:]
c.IntervalSet = append(c.IntervalSet[:left], newInterval)
c.IntervalSet = append(c.IntervalSet, tmp...)
c.IntervalSet = slices.Replace(c.IntervalSet, left, right, v)
}

// AddHole updates the current CanonicalIntervalSet object by removing the input Interval from the set
Expand Down Expand Up @@ -189,30 +94,23 @@ func (c *CanonicalIntervalSet) Copy() CanonicalIntervalSet {
return CanonicalIntervalSet{IntervalSet: append([]Interval(nil), c.IntervalSet...)}
}

/*func Union(a, b CanonicalIntervalSet) CanonicalIntervalSet {
res := a.Copy()
res.Union(b)
return res
}*/

func (c *CanonicalIntervalSet) Contains(n int64) bool {
i := CreateFromInterval(n, n)
return i.ContainedIn(*c)
}

// ContainedIn returns true of the current CanonicalIntervalSet is contained in the input CanonicalIntervalSet
func (c *CanonicalIntervalSet) ContainedIn(other CanonicalIntervalSet) bool {
if len(c.IntervalSet) == 1 && len(other.IntervalSet) == 1 {
return c.IntervalSet[0].isSubset(other.IntervalSet[0])
}
for _, interval := range c.IntervalSet {
left := other.findIntervalLeft(interval)
if left == len(other.IntervalSet)-1 {
return false
}
if !interval.isSubset(other.IntervalSet[left+1]) {
larger := other.IntervalSet
for _, target := range c.IntervalSet {
left := sort.Search(len(larger), func(i int) bool {
return larger[i].End >= target.End
})
if left == len(larger) || larger[left].Start > target.Start {
return false
}
// Optimization
larger = larger[left:]
}
return true
}
Expand Down

0 comments on commit e1d68f7

Please sign in to comment.