🎨 优化单元测试及示例
This commit is contained in:
parent
1059f9c016
commit
ea7ed7ea4e
|
@ -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
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 #
|
||||||
|
}
|
|
@ -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
|
||||||
|
|
|
@ -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)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue