mirror of
https://github.com/yusing/godoxy.git
synced 2026-01-11 22:30:47 +01: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