mirror of
https://github.com/SagerNet/sing.git
synced 2025-04-03 20:07:38 +03:00
Add range api
This commit is contained in:
parent
eb2fad956a
commit
4f2b872a8c
5 changed files with 330 additions and 20 deletions
9
.github/workflows/debug.yml
vendored
9
.github/workflows/debug.yml
vendored
|
@ -30,7 +30,7 @@ jobs:
|
|||
uses: actions/setup-go@v2
|
||||
with:
|
||||
go-version: ${{ steps.version.outputs.go_version }}
|
||||
- name: Build
|
||||
- name: Add cache to Go proxy
|
||||
run: |
|
||||
version=`git rev-parse HEAD`
|
||||
mkdir build
|
||||
|
@ -38,4 +38,9 @@ jobs:
|
|||
go mod init build
|
||||
go get -v github.com/sagernet/sing@$version
|
||||
popd
|
||||
go build -v ./...
|
||||
continue-on-error: true
|
||||
- name: Build
|
||||
run: |
|
||||
make test
|
||||
make lint_install
|
||||
make lint
|
|
@ -3,14 +3,14 @@ linters:
|
|||
enable:
|
||||
- gofumpt
|
||||
- govet
|
||||
- gci
|
||||
# - gci
|
||||
- staticcheck
|
||||
|
||||
linters-settings:
|
||||
gci:
|
||||
sections:
|
||||
- standard
|
||||
- prefix(github.com/sagernet/)
|
||||
- default
|
||||
# gci:
|
||||
# sections:
|
||||
# - standard
|
||||
# - prefix(github.com/sagernet/)
|
||||
# - default
|
||||
staticcheck:
|
||||
go: '1.18'
|
||||
go: '1.19'
|
||||
|
|
21
Makefile
21
Makefile
|
@ -1,10 +1,11 @@
|
|||
fmt:
|
||||
gofumpt -l -w .
|
||||
gofmt -s -w .
|
||||
gci write -s "standard,prefix(github.com/sagernet/),default" .
|
||||
@gofumpt -l -w .
|
||||
@gofmt -s -w .
|
||||
@gci write -s "standard,prefix(github.com/sagernet/),default" .
|
||||
|
||||
lint_install:
|
||||
go install github.com/golangci/golangci-lint/cmd/golangci-lint@latest
|
||||
fmt_install:
|
||||
go install -v mvdan.cc/gofumpt@latest
|
||||
go install -v github.com/daixiang0/gci@v0.4.0
|
||||
|
||||
lint:
|
||||
GOOS=linux golangci-lint run ./...
|
||||
|
@ -12,10 +13,8 @@ lint:
|
|||
GOOS=darwin golangci-lint run ./...
|
||||
GOOS=freebsd golangci-lint run ./...
|
||||
|
||||
test:
|
||||
go test -v .
|
||||
lint_install:
|
||||
go install github.com/golangci/golangci-lint/cmd/golangci-lint@latest
|
||||
|
||||
update:
|
||||
git fetch
|
||||
git reset FETCH_HEAD --hard
|
||||
git clean -fdx
|
||||
test:
|
||||
go test -v ./...
|
112
common/ranges/range.go
Normal file
112
common/ranges/range.go
Normal file
|
@ -0,0 +1,112 @@
|
|||
package ranges
|
||||
|
||||
import (
|
||||
"sort"
|
||||
|
||||
"github.com/sagernet/sing/common/x/constraints"
|
||||
)
|
||||
|
||||
type Range[N comparable] struct {
|
||||
Start N
|
||||
End N
|
||||
}
|
||||
|
||||
func New[N constraints.Integer](start N, end N) Range[N] {
|
||||
return Range[N]{start, end}
|
||||
}
|
||||
|
||||
func NewSingle[N constraints.Integer](index N) Range[N] {
|
||||
return Range[N]{index, index}
|
||||
}
|
||||
|
||||
func Merge[N constraints.Integer](ranges []Range[N]) (mergedRanges []Range[N]) {
|
||||
if len(ranges) == 0 {
|
||||
return
|
||||
}
|
||||
sort.Slice(ranges, func(i, j int) bool {
|
||||
return ranges[i].Start < ranges[j].Start
|
||||
})
|
||||
mergedRanges = ranges[:1]
|
||||
var rangeIndex N
|
||||
for _, r := range ranges[1:] {
|
||||
if r.Start > mergedRanges[rangeIndex].End+1 {
|
||||
mergedRanges = append(mergedRanges, r)
|
||||
rangeIndex++
|
||||
} else if r.End > mergedRanges[rangeIndex].End {
|
||||
mergedRanges[rangeIndex].End = r.End
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func Revert[N constraints.Integer](start, end N, ranges []Range[N]) (revertedRanges []Range[N]) {
|
||||
if len(ranges) == 0 {
|
||||
return
|
||||
}
|
||||
ranges = Merge(ranges)
|
||||
if ranges[0].Start > start {
|
||||
revertedRanges = append(revertedRanges, Range[N]{start, ranges[0].Start - 1})
|
||||
}
|
||||
rangeEnd := ranges[0].End
|
||||
for _, r := range ranges[1:] {
|
||||
if r.Start > rangeEnd+1 {
|
||||
revertedRanges = append(revertedRanges, Range[N]{rangeEnd + 1, r.Start - 1})
|
||||
}
|
||||
rangeEnd = r.End
|
||||
}
|
||||
if end > rangeEnd {
|
||||
revertedRanges = append(revertedRanges, Range[N]{rangeEnd + 1, end})
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func Exclude[N constraints.Integer](ranges []Range[N], targetRanges []Range[N]) []Range[N] {
|
||||
ranges = Merge(ranges)
|
||||
if len(ranges) == 0 {
|
||||
return nil
|
||||
}
|
||||
targetRanges = Merge(targetRanges)
|
||||
if len(targetRanges) == 0 {
|
||||
return ranges
|
||||
}
|
||||
var mergedRanges []Range[N]
|
||||
rangeStart := ranges[0].Start
|
||||
rangeEnd := ranges[0].End
|
||||
rangeIndex := rangeStart
|
||||
ranges = ranges[1:]
|
||||
targetStart := targetRanges[0].Start
|
||||
targetEnd := targetRanges[0].End
|
||||
targetRanges = targetRanges[1:]
|
||||
for {
|
||||
if targetStart > rangeEnd {
|
||||
if rangeIndex <= rangeEnd {
|
||||
mergedRanges = append(mergedRanges, Range[N]{rangeIndex, rangeEnd})
|
||||
}
|
||||
if len(ranges) == 0 {
|
||||
break
|
||||
}
|
||||
rangeStart = ranges[0].Start
|
||||
rangeEnd = ranges[0].End
|
||||
rangeIndex = rangeStart
|
||||
ranges = ranges[1:]
|
||||
continue
|
||||
}
|
||||
if targetStart > rangeIndex {
|
||||
mergedRanges = append(mergedRanges, Range[N]{rangeIndex, targetStart - 1})
|
||||
rangeIndex = targetStart + 1
|
||||
}
|
||||
if targetEnd <= rangeEnd {
|
||||
rangeIndex = targetEnd + 1
|
||||
if len(targetRanges) == 0 {
|
||||
break
|
||||
}
|
||||
targetStart = targetRanges[0].Start
|
||||
targetEnd = targetRanges[0].End
|
||||
targetRanges = targetRanges[1:]
|
||||
}
|
||||
}
|
||||
if rangeIndex <= rangeEnd {
|
||||
mergedRanges = append(mergedRanges, Range[N]{rangeIndex, rangeEnd})
|
||||
}
|
||||
return Merge(append(mergedRanges, ranges...))
|
||||
}
|
194
common/ranges/range_test.go
Normal file
194
common/ranges/range_test.go
Normal file
|
@ -0,0 +1,194 @@
|
|||
package ranges
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestRevertRanges(t *testing.T) {
|
||||
for _, testRange := range []struct {
|
||||
start, end int
|
||||
ranges []Range[int]
|
||||
expected []Range[int]
|
||||
}{
|
||||
{
|
||||
start: 0,
|
||||
end: 10,
|
||||
ranges: []Range[int]{
|
||||
{0, 1},
|
||||
},
|
||||
expected: []Range[int]{
|
||||
{2, 10},
|
||||
},
|
||||
},
|
||||
{
|
||||
start: 0,
|
||||
end: 10,
|
||||
ranges: []Range[int]{
|
||||
{9, 10},
|
||||
},
|
||||
expected: []Range[int]{
|
||||
{0, 8},
|
||||
},
|
||||
},
|
||||
{
|
||||
start: 0,
|
||||
end: 10,
|
||||
ranges: []Range[int]{
|
||||
{0, 1},
|
||||
{9, 10},
|
||||
},
|
||||
expected: []Range[int]{
|
||||
{2, 8},
|
||||
},
|
||||
},
|
||||
{
|
||||
start: 0,
|
||||
end: 10,
|
||||
ranges: []Range[int]{
|
||||
{2, 4},
|
||||
{6, 8},
|
||||
},
|
||||
expected: []Range[int]{
|
||||
{0, 1},
|
||||
{5, 5},
|
||||
{9, 10},
|
||||
},
|
||||
},
|
||||
{
|
||||
start: 0,
|
||||
end: 10,
|
||||
ranges: []Range[int]{
|
||||
{2, 4},
|
||||
{8, 9},
|
||||
},
|
||||
expected: []Range[int]{
|
||||
{0, 1},
|
||||
{5, 7},
|
||||
{10, 10},
|
||||
},
|
||||
},
|
||||
} {
|
||||
result := Revert(testRange.start, testRange.end, testRange.ranges)
|
||||
if !reflect.DeepEqual(result, testRange.expected) {
|
||||
t.Fatal("expected", testRange.expected, "\ngot", result)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestMergeRanges(t *testing.T) {
|
||||
for _, testRange := range []struct {
|
||||
ranges []Range[int]
|
||||
expected []Range[int]
|
||||
}{
|
||||
{
|
||||
ranges: []Range[int]{
|
||||
{0, 1},
|
||||
{1, 2},
|
||||
},
|
||||
expected: []Range[int]{
|
||||
{0, 2},
|
||||
},
|
||||
},
|
||||
{
|
||||
ranges: []Range[int]{
|
||||
{0, 3},
|
||||
{5, 7},
|
||||
{8, 9},
|
||||
{10, 10},
|
||||
},
|
||||
expected: []Range[int]{
|
||||
{0, 3},
|
||||
{5, 10},
|
||||
},
|
||||
},
|
||||
{
|
||||
ranges: []Range[int]{
|
||||
{1, 3},
|
||||
{2, 6},
|
||||
{8, 10},
|
||||
{15, 18},
|
||||
},
|
||||
expected: []Range[int]{
|
||||
{1, 6},
|
||||
{8, 10},
|
||||
{15, 18},
|
||||
},
|
||||
},
|
||||
{
|
||||
ranges: []Range[int]{
|
||||
{1, 3},
|
||||
{2, 7},
|
||||
{2, 6},
|
||||
},
|
||||
expected: []Range[int]{
|
||||
{1, 7},
|
||||
},
|
||||
},
|
||||
{
|
||||
ranges: []Range[int]{
|
||||
{1, 3},
|
||||
{2, 6},
|
||||
{2, 7},
|
||||
},
|
||||
expected: []Range[int]{
|
||||
{1, 7},
|
||||
},
|
||||
},
|
||||
} {
|
||||
result := Merge(testRange.ranges)
|
||||
if !reflect.DeepEqual(result, testRange.expected) {
|
||||
t.Fatal("input", testRange.ranges, "\nexpected", testRange.expected, "\ngot", result)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestExcludeRanges(t *testing.T) {
|
||||
for _, testRange := range []struct {
|
||||
ranges []Range[int]
|
||||
exclude []Range[int]
|
||||
expected []Range[int]
|
||||
}{
|
||||
{
|
||||
ranges: []Range[int]{
|
||||
{0, 100},
|
||||
},
|
||||
exclude: []Range[int]{
|
||||
{0, 10},
|
||||
{20, 30},
|
||||
{55, 55},
|
||||
},
|
||||
expected: []Range[int]{
|
||||
{11, 19},
|
||||
{31, 54},
|
||||
{56, 100},
|
||||
},
|
||||
},
|
||||
{
|
||||
ranges: []Range[int]{
|
||||
{0, 100},
|
||||
{200, 300},
|
||||
},
|
||||
exclude: []Range[int]{
|
||||
{0, 10},
|
||||
{20, 30},
|
||||
{55, 55},
|
||||
{250, 250},
|
||||
{299, 299},
|
||||
},
|
||||
expected: []Range[int]{
|
||||
{11, 19},
|
||||
{31, 54},
|
||||
{56, 100},
|
||||
{200, 249},
|
||||
{251, 298},
|
||||
{300, 300},
|
||||
},
|
||||
},
|
||||
} {
|
||||
result := Exclude(testRange.ranges, testRange.exclude)
|
||||
if !reflect.DeepEqual(result, testRange.expected) {
|
||||
t.Fatal("input", testRange.ranges, testRange.exclude, "\nexpected", testRange.expected, "\ngot", result)
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue