🎨 优化单元测试及示例

This commit is contained in:
kercylan98 2023-06-19 10:52:53 +08:00
parent 1059f9c016
commit ea7ed7ea4e
4 changed files with 303 additions and 63 deletions

View File

@ -1,16 +1,49 @@
package geometry package geometry
import ( import (
"bytes"
"fmt"
"github.com/kercylan98/minotaur/utils/generic" "github.com/kercylan98/minotaur/utils/generic"
"github.com/kercylan98/minotaur/utils/slice" "github.com/kercylan98/minotaur/utils/slice"
"math" "math"
"sort" "sort"
"strings"
) )
var ( var (
ShapeStringHasBorder = false // 控制 Shape.String 是否拥有边界 ShapeStringHasBorder = false // 控制 Shape.String 是否拥有边界
) )
// NewShape 通过多个点生成一个形状进行返回
func NewShape[V generic.SignedNumber](points ...Point[V]) Shape[V] {
return points
}
// NewShapeWithString 通过字符串将指定 rune 转换为点位置生成形状进行返回
// - 每个点的顺序从上到下,从左到右
func NewShapeWithString[V generic.SignedNumber](rows []string, point rune) (shape Shape[V]) {
var width int
for _, row := range rows {
length := len(row)
if length > width {
width = length
}
}
for y := 0; y < len(rows); y++ {
runes := bytes.Runes([]byte(rows[y]))
for x := 0; x < width; x++ {
if x >= len(runes) {
break
}
if point == runes[x] {
shape = append(shape, NewPoint(V(x), V(y)))
}
}
}
return shape
}
// Shape 通过多个点表示了一个形状 // Shape 通过多个点表示了一个形状
type Shape[V generic.SignedNumber] []Point[V] type Shape[V generic.SignedNumber] []Point[V]
@ -19,6 +52,11 @@ func (slf Shape[V]) Points() []Point[V] {
return slf return slf
} }
// PointCount 获取这个形状的点数量
func (slf Shape[V]) PointCount() int {
return len(slf)
}
// String 将该形状转换为可视化的字符串进行返回 // String 将该形状转换为可视化的字符串进行返回
func (slf Shape[V]) String() string { func (slf Shape[V]) String() string {
var result string var result string
@ -41,9 +79,18 @@ func (slf Shape[V]) String() string {
result += "# " result += "# "
} }
} }
result += "\r\n" result = result[:len(result)-1]
result += "\n"
} }
} else { } else {
if left < 0 {
left += -left
right += -left
}
if top < 0 {
top += -top
bottom += -top
}
for y := V(0); y < top+height; y++ { for y := V(0); y < top+height; y++ {
for x := V(0); x < left+width; x++ { for x := V(0); x < left+width; x++ {
exist := false exist := false
@ -59,10 +106,11 @@ func (slf Shape[V]) String() string {
result += "# " result += "# "
} }
} }
result += "\r\n" result = result[:len(result)-1]
result += "\n"
} }
} }
return result return fmt.Sprintf("%v\n%s", slf.Points(), strings.TrimSuffix(result, "\n"))
} }
// ShapeSearch 获取该形状中包含的所有图形组合及其位置 // ShapeSearch 获取该形状中包含的所有图形组合及其位置
@ -72,7 +120,7 @@ func (slf Shape[V]) String() string {
// //
// 可通过可选项对搜索结果进行过滤 // 可通过可选项对搜索结果进行过滤
func (slf Shape[V]) ShapeSearch(options ...ShapeSearchOption) (result []Shape[V]) { func (slf Shape[V]) ShapeSearch(options ...ShapeSearchOption) (result []Shape[V]) {
opt := &shapeSearchOptions{upperLimit: math.MaxInt, rectangleMaxWidth: math.MaxInt, rectangleMaxHeight: math.MaxInt} opt := newShapeSearchOptions()
opt.directionCountUpper = map[Direction]int{} opt.directionCountUpper = map[Direction]int{}
for _, d := range DirectionUDLR { for _, d := range DirectionUDLR {
opt.directionCountUpper[d] = math.MaxInt opt.directionCountUpper[d] = math.MaxInt
@ -143,7 +191,7 @@ func (slf Shape[V]) getAllGraphicComposition(opt *shapeSearchOptions) (result []
height := bottom - top + 1 height := bottom - top + 1
areaWidth := width + left areaWidth := width + left
areaHeight := height + top areaHeight := height + top
rectangleShape := GenerateShapeOnRectangle(slf.Points()...) rectangleShape := GenerateShapeOnRectangleWithCoordinate(slf.Points()...)
records := make(map[V]struct{}) records := make(map[V]struct{})
var match = func(links Shape[V], directionCount map[Direction]int, count int) bool { var match = func(links Shape[V], directionCount map[Direction]int, count int) bool {
@ -229,53 +277,55 @@ func (slf Shape[V]) getAllGraphicComposition(opt *shapeSearchOptions) (result []
// 搜索四个方向 // 搜索四个方向
var next = -1 var next = -1
var directionPoint = point var directionPoint = point
var links = Shape[V]{point} var links = Shape[V]{}
var linkRecord = map[V]struct{}{}
var directionCount = map[Direction]int{} var directionCount = map[Direction]int{}
var count = 0 var count = 0
for i, directions := range [][]Direction{DirectionUDLR, DirectionLRUD} { for i, directions := range [][]Direction{DirectionUDLR, DirectionLRUD} {
var direction Direction var direction Direction
next, direction = slice.NextLoop(directions, next)
for { for {
directionPoint = GetDirectionNextWithCoordinateArray(direction, directionPoint) next, direction = slice.NextLoop(directions, next)
if px, py := directionPoint.GetXY(); px < 0 || px >= areaWidth || py < 0 || py >= areaHeight { for {
break directionPoint = GetDirectionNextWithCoordinateArray(direction, directionPoint)
} if px, py := directionPoint.GetXY(); px < 0 || px >= areaWidth || py < 0 || py >= areaHeight {
offset := directionPoint.GetOffset(-left, -top) break
if offset.Negative() { }
break offset := directionPoint.GetOffset(-left, -top)
} if offset.OutOf(V(0), V(0), width, height) || !rectangleShape[int(offset.GetX())][int(offset.GetY())] {
offsetPos := int(offset.GetPos(width)) break
if offsetPos < 0 || offsetPos >= len(rectangleShape) || !rectangleShape[offsetPos].Data { }
break recordPos := directionPoint.GetPos(areaWidth)
} if _, exist := linkRecord[recordPos]; !exist {
links = append(links, directionPoint) linkRecord[recordPos] = struct{}{}
directionCount[direction]++ links = append(links, directionPoint)
count++ directionCount[direction]++
match(links, directionCount, count) count++
match(links, directionCount, count)
}
pos := directionPoint.GetPos(areaWidth) pos := directionPoint.GetPos(areaWidth)
if _, exist := records[pos]; !exist { if _, exist := records[pos]; !exist && opt.directionCount < 1 {
result = append(result, Shape[V]{directionPoint}) result = append(result, Shape[V]{directionPoint})
records[pos] = struct{}{} records[pos] = struct{}{}
}
} }
} finish := false
switch i {
finish := false case 0:
switch i { if direction == DirectionRight {
case 0: finish = true
if direction == DirectionRight { }
finish = true case 1:
if direction == DirectionDown {
finish = true
}
} }
case 1: if finish {
if direction == DirectionDown { break
finish = true
} }
directionPoint = point
} }
if finish {
break
}
directionPoint = point
} }
} }

View File

@ -0,0 +1,104 @@
package geometry_test
import (
"fmt"
"github.com/kercylan98/minotaur/utils/geometry"
)
func ExampleNewShape() {
shape := geometry.NewShape[int](
geometry.NewPoint(3, 0),
geometry.NewPoint(3, 1),
geometry.NewPoint(3, 2),
geometry.NewPoint(3, 3),
geometry.NewPoint(4, 3),
)
fmt.Println(shape)
// Output:
// [[3 0] [3 1] [3 2] [3 3] [4 3]]
// X #
// X #
// X #
// X X
}
func ExampleNewShapeWithString() {
shape := geometry.NewShapeWithString[int]([]string{
"###X###",
"###X###",
"###X###",
"###XX##",
}, 'X')
fmt.Println(shape)
// Output: [[3 0] [3 1] [3 2] [3 3] [4 3]]
// X #
// X #
// X #
// X X
}
func ExampleShape_Points() {
shape := geometry.NewShapeWithString[int]([]string{
"###X###",
"##XXX##",
}, 'X')
points := shape.Points()
fmt.Println(points)
// Output:
// [[3 0] [2 1] [3 1] [4 1]]
}
func ExampleShape_PointCount() {
shape := geometry.NewShapeWithString[int]([]string{
"###X###",
"##XXX##",
}, 'X')
fmt.Println(shape.PointCount())
// Output:
// 4
}
func ExampleShape_String() {
shape := geometry.NewShapeWithString[int]([]string{
"###X###",
"##XXX##",
}, 'X')
fmt.Println(shape)
// Output:
// [[3 0] [2 1] [3 1] [4 1]]
// # X #
// X X X
}
func ExampleShape_ShapeSearch() {
shape := geometry.NewShapeWithString[int]([]string{
"###X###",
"##XXX##",
"###X###",
}, 'X')
shapes := shape.ShapeSearch(
geometry.WithShapeSearchDeduplication(),
geometry.WithShapeSearchDesc(),
)
for _, shape := range shapes {
fmt.Println(shape)
}
// Output:
// [[3 0] [3 2] [2 1] [4 1] [3 1]]
// # X #
// X X X
// # X #
}

View File

@ -1,5 +1,15 @@
package geometry package geometry
import "math"
func newShapeSearchOptions() *shapeSearchOptions {
return &shapeSearchOptions{
upperLimit: math.MaxInt,
rectangleMaxWidth: math.MaxInt,
rectangleMaxHeight: math.MaxInt,
}
}
type shapeSearchOptions struct { type shapeSearchOptions struct {
lowerLimit int lowerLimit int
upperLimit int upperLimit int

View File

@ -3,30 +3,106 @@ package geometry_test
import ( import (
"fmt" "fmt"
"github.com/kercylan98/minotaur/utils/geometry" "github.com/kercylan98/minotaur/utils/geometry"
. "github.com/smartystreets/goconvey/convey"
"testing" "testing"
) )
func TestShape_Search(t *testing.T) { func TestNewShape(t *testing.T) {
var shape geometry.Shape[int] Convey("TestNewShape", t, func() {
// 生成一个L形的shape shape := geometry.NewShape[int](
shape = append(shape, geometry.NewPoint(1, 0)) geometry.NewPoint(3, 0),
shape = append(shape, geometry.NewPoint(1, 1)) geometry.NewPoint(3, 1),
shape = append(shape, geometry.NewPoint(1, 2)) geometry.NewPoint(3, 2),
shape = append(shape, geometry.NewPoint(2, 1)) geometry.NewPoint(3, 3),
shape = append(shape, geometry.NewPoint(2, 2)) geometry.NewPoint(4, 3),
shape = append(shape, geometry.NewPoint(1, 3)) )
geometry.ShapeStringHasBorder = true
fmt.Println("形状:")
fmt.Println(shape)
shapes := shape.ShapeSearch(
geometry.WithShapeSearchDesc(),
geometry.WithShapeSearchRectangleLowerLimit(2, 2),
)
for _, shape := range shapes {
fmt.Println("搜索", shape.Points())
fmt.Println(shape) fmt.Println(shape)
} points := shape.Points()
count := shape.PointCount()
So(count, ShouldEqual, 5)
So(points[0], ShouldEqual, geometry.NewPoint(3, 0))
So(points[1], ShouldEqual, geometry.NewPoint(3, 1))
So(points[2], ShouldEqual, geometry.NewPoint(3, 2))
So(points[3], ShouldEqual, geometry.NewPoint(3, 3))
So(points[4], ShouldEqual, geometry.NewPoint(4, 3))
})
}
func TestNewShapeWithString(t *testing.T) {
Convey("TestNewShapeWithString", t, func() {
shape := geometry.NewShapeWithString[int]([]string{
"###X###",
"###X###",
"###X###",
"###XX##",
}, 'X')
points := shape.Points()
count := shape.PointCount()
So(count, ShouldEqual, 5)
So(points[0], ShouldEqual, geometry.NewPoint(3, 0))
So(points[1], ShouldEqual, geometry.NewPoint(3, 1))
So(points[2], ShouldEqual, geometry.NewPoint(3, 2))
So(points[3], ShouldEqual, geometry.NewPoint(3, 3))
So(points[4], ShouldEqual, geometry.NewPoint(4, 3))
})
}
func TestShape_Points(t *testing.T) {
Convey("TestShape_Points", t, func() {
shape := geometry.NewShapeWithString[int]([]string{
"###X###",
"##XXX##",
}, 'X')
points := shape.Points()
So(points[0], ShouldEqual, geometry.NewPoint(3, 0))
So(points[1], ShouldEqual, geometry.NewPoint(2, 1))
So(points[2], ShouldEqual, geometry.NewPoint(3, 1))
So(points[3], ShouldEqual, geometry.NewPoint(4, 1))
})
}
func TestShape_PointCount(t *testing.T) {
Convey("TestShape_PointCount", t, func() {
shape := geometry.NewShapeWithString[int]([]string{
"###X###",
"##XXX##",
}, 'X')
So(shape.PointCount(), ShouldEqual, 4)
})
}
func TestShape_String(t *testing.T) {
Convey("TestShape_String", t, func() {
shape := geometry.NewShapeWithString[int]([]string{
"###X###",
"##XXX##",
}, 'X')
str := shape.String()
So(str, ShouldEqual, "[[3 0] [2 1] [3 1] [4 1]]\n# X #\nX X X")
})
}
func TestShape_Search(t *testing.T) {
Convey("TestShape_Search", t, func() {
shape := geometry.NewShapeWithString[int]([]string{
"###X###",
"##XXX##",
"###X###",
}, 'X')
shapes := shape.ShapeSearch(
geometry.WithShapeSearchDeduplication(),
geometry.WithShapeSearchDesc(),
)
So(len(shapes), ShouldEqual, 1)
for _, shape := range shapes {
So(shape.PointCount(), ShouldEqual, 5)
}
})
} }