mirror of
https://github.com/yusing/godoxy.git
synced 2026-04-05 08:27:05 +02:00
chore: remove unused utils/deep_equal.go
This commit is contained in:
@@ -1,243 +0,0 @@
|
|||||||
package utils
|
|
||||||
|
|
||||||
import (
|
|
||||||
"reflect"
|
|
||||||
"unsafe"
|
|
||||||
)
|
|
||||||
|
|
||||||
// DeepEqual reports whether x and y are deeply equal.
|
|
||||||
// It supports numerics, strings, maps, slices, arrays, and structs (exported fields only).
|
|
||||||
// It's optimized for performance by avoiding reflection for common types and
|
|
||||||
// adaptively choosing between BFS and DFS traversal strategies.
|
|
||||||
func DeepEqual(x, y any) bool {
|
|
||||||
if x == nil || y == nil {
|
|
||||||
return x == y
|
|
||||||
}
|
|
||||||
|
|
||||||
v1 := reflect.ValueOf(x)
|
|
||||||
v2 := reflect.ValueOf(y)
|
|
||||||
|
|
||||||
if v1.Type() != v2.Type() {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
return deepEqual(v1, v2, make(map[visit]bool), 0)
|
|
||||||
}
|
|
||||||
|
|
||||||
// visit represents a visit to a pair of values during comparison
|
|
||||||
type visit struct {
|
|
||||||
a1, a2 unsafe.Pointer
|
|
||||||
typ reflect.Type
|
|
||||||
}
|
|
||||||
|
|
||||||
// deepEqual performs the actual deep comparison with cycle detection
|
|
||||||
func deepEqual(v1, v2 reflect.Value, visited map[visit]bool, depth int) bool {
|
|
||||||
if !v1.IsValid() || !v2.IsValid() {
|
|
||||||
return v1.IsValid() == v2.IsValid()
|
|
||||||
}
|
|
||||||
|
|
||||||
if v1.Type() != v2.Type() {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle cycle detection for pointer-like types
|
|
||||||
if v1.CanAddr() && v2.CanAddr() {
|
|
||||||
addr1 := unsafe.Pointer(v1.UnsafeAddr())
|
|
||||||
addr2 := unsafe.Pointer(v2.UnsafeAddr())
|
|
||||||
typ := v1.Type()
|
|
||||||
v := visit{addr1, addr2, typ}
|
|
||||||
if visited[v] {
|
|
||||||
return true // already visiting, assume equal
|
|
||||||
}
|
|
||||||
visited[v] = true
|
|
||||||
defer delete(visited, v)
|
|
||||||
}
|
|
||||||
|
|
||||||
switch v1.Kind() {
|
|
||||||
case reflect.Bool:
|
|
||||||
return v1.Bool() == v2.Bool()
|
|
||||||
|
|
||||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
|
||||||
return v1.Int() == v2.Int()
|
|
||||||
|
|
||||||
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
|
|
||||||
return v1.Uint() == v2.Uint()
|
|
||||||
|
|
||||||
case reflect.Float32, reflect.Float64:
|
|
||||||
return floatEqual(v1.Float(), v2.Float())
|
|
||||||
|
|
||||||
case reflect.Complex64, reflect.Complex128:
|
|
||||||
c1, c2 := v1.Complex(), v2.Complex()
|
|
||||||
return floatEqual(real(c1), real(c2)) && floatEqual(imag(c1), imag(c2))
|
|
||||||
|
|
||||||
case reflect.String:
|
|
||||||
return v1.String() == v2.String()
|
|
||||||
|
|
||||||
case reflect.Array:
|
|
||||||
return deepEqualArray(v1, v2, visited, depth)
|
|
||||||
|
|
||||||
case reflect.Slice:
|
|
||||||
return deepEqualSlice(v1, v2, visited, depth)
|
|
||||||
|
|
||||||
case reflect.Map:
|
|
||||||
return deepEqualMap(v1, v2, visited, depth)
|
|
||||||
|
|
||||||
case reflect.Struct:
|
|
||||||
return deepEqualStruct(v1, v2, visited, depth)
|
|
||||||
|
|
||||||
case reflect.Ptr:
|
|
||||||
if v1.IsNil() || v2.IsNil() {
|
|
||||||
return v1.IsNil() && v2.IsNil()
|
|
||||||
}
|
|
||||||
return deepEqual(v1.Elem(), v2.Elem(), visited, depth+1)
|
|
||||||
|
|
||||||
case reflect.Interface:
|
|
||||||
if v1.IsNil() || v2.IsNil() {
|
|
||||||
return v1.IsNil() && v2.IsNil()
|
|
||||||
}
|
|
||||||
return deepEqual(v1.Elem(), v2.Elem(), visited, depth+1)
|
|
||||||
|
|
||||||
default:
|
|
||||||
// For unsupported types (func, chan, etc.), fall back to basic equality
|
|
||||||
return v1.Interface() == v2.Interface()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// floatEqual handles NaN cases properly
|
|
||||||
func floatEqual(f1, f2 float64) bool {
|
|
||||||
return f1 == f2 || (f1 != f1 && f2 != f2) // NaN == NaN
|
|
||||||
}
|
|
||||||
|
|
||||||
// deepEqualArray compares arrays using DFS (since arrays have fixed size)
|
|
||||||
func deepEqualArray(v1, v2 reflect.Value, visited map[visit]bool, depth int) bool {
|
|
||||||
for i := range v1.Len() {
|
|
||||||
if !deepEqual(v1.Index(i), v2.Index(i), visited, depth+1) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
// deepEqualSlice compares slices, choosing strategy based on size and depth
|
|
||||||
func deepEqualSlice(v1, v2 reflect.Value, visited map[visit]bool, depth int) bool {
|
|
||||||
if v1.IsNil() != v2.IsNil() {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
if v1.Len() != v2.Len() {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
if v1.IsNil() {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
// Use BFS for large slices at shallow depth to improve cache locality
|
|
||||||
// Use DFS for small slices or deep nesting to reduce memory overhead
|
|
||||||
if shouldUseBFS(v1.Len(), depth) {
|
|
||||||
return deepEqualSliceBFS(v1, v2, visited, depth)
|
|
||||||
}
|
|
||||||
return deepEqualSliceDFS(v1, v2, visited, depth)
|
|
||||||
}
|
|
||||||
|
|
||||||
// deepEqualSliceDFS uses depth-first traversal
|
|
||||||
func deepEqualSliceDFS(v1, v2 reflect.Value, visited map[visit]bool, depth int) bool {
|
|
||||||
for i := range v1.Len() {
|
|
||||||
if !deepEqual(v1.Index(i), v2.Index(i), visited, depth+1) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
// deepEqualSliceBFS uses breadth-first traversal for better cache locality
|
|
||||||
func deepEqualSliceBFS(v1, v2 reflect.Value, visited map[visit]bool, depth int) bool {
|
|
||||||
length := v1.Len()
|
|
||||||
|
|
||||||
// First, check all direct elements
|
|
||||||
for i := range length {
|
|
||||||
elem1, elem2 := v1.Index(i), v2.Index(i)
|
|
||||||
|
|
||||||
// For simple types, compare directly
|
|
||||||
if isSimpleType(elem1.Kind()) {
|
|
||||||
if !deepEqual(elem1, elem2, visited, depth+1) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Then, recursively check complex elements
|
|
||||||
for i := range length {
|
|
||||||
elem1, elem2 := v1.Index(i), v2.Index(i)
|
|
||||||
|
|
||||||
if !isSimpleType(elem1.Kind()) {
|
|
||||||
if !deepEqual(elem1, elem2, visited, depth+1) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
// deepEqualMap compares maps
|
|
||||||
func deepEqualMap(v1, v2 reflect.Value, visited map[visit]bool, depth int) bool {
|
|
||||||
if v1.IsNil() != v2.IsNil() {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
if v1.Len() != v2.Len() {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
if v1.IsNil() {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check all keys and values
|
|
||||||
for _, key := range v1.MapKeys() {
|
|
||||||
val1 := v1.MapIndex(key)
|
|
||||||
val2 := v2.MapIndex(key)
|
|
||||||
|
|
||||||
if !val2.IsValid() {
|
|
||||||
return false // key doesn't exist in v2
|
|
||||||
}
|
|
||||||
|
|
||||||
if !deepEqual(val1, val2, visited, depth+1) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
// deepEqualStruct compares structs (exported fields only)
|
|
||||||
func deepEqualStruct(v1, v2 reflect.Value, visited map[visit]bool, depth int) bool {
|
|
||||||
typ := v1.Type()
|
|
||||||
|
|
||||||
for i := range typ.NumField() {
|
|
||||||
field := typ.Field(i)
|
|
||||||
|
|
||||||
// Skip unexported fields
|
|
||||||
if !field.IsExported() {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if !deepEqual(v1.Field(i), v2.Field(i), visited, depth+1) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
// shouldUseBFS determines whether to use BFS or DFS based on slice size and depth
|
|
||||||
func shouldUseBFS(length, depth int) bool {
|
|
||||||
// Use BFS for large slices at shallow depth (better cache locality)
|
|
||||||
// Use DFS for small slices or deep nesting (lower memory overhead)
|
|
||||||
return length > 100 && depth < 3
|
|
||||||
}
|
|
||||||
|
|
||||||
// isSimpleType checks if a type can be compared without deep recursion
|
|
||||||
func isSimpleType(kind reflect.Kind) bool {
|
|
||||||
if kind >= reflect.Bool && kind <= reflect.Complex128 {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
return kind == reflect.String
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user