UniCoreFW

is_equal()

Deep equality with cycle handling and fast paths.

Implementation

Semantics: - Requires exact same type (type(a) is type(b)), like your original. - dict/Mapping: keys must match; values compared deeply. - list/tuple/Sequence (not str/bytes/bytearray): order matters. - set/frozenset/Set: order doesn't matter (direct equality). - Other types fall back to ==. - No special-casing for NaN (NaN != NaN), preserving original behavior. Complexity: O(N) in the total number of elements visited across both structures. Args: obj1: First object to compare obj2: Second object to compare Returns: True if objects are deeply equal, False otherwise

Example

is_equal([1, 2, 3], [1, 2, 3])

Expected output: True

Source Code

def is_equal(obj1: Any, obj2: Any) -> bool: if obj1 is obj2: return True if type(obj1) is not type(obj2): return False # Fast-path for common immutables if isinstance(obj1, (int, float, str, bool, type(None), bytes, bytearray, memoryview, range)): return obj1 == obj2 # Iterative DFS with cycle detection on (id(a), id(b)) pairs seen: set[Tuple[int, int]] = set() stack: List[Tuple[Any, Any]] = [(obj1, obj2)] while stack: a, b = stack.pop() if a is b: continue if type(a) is not type(b): return False pid = (id(a), id(b)) if pid in seen: continue seen.add(pid) # Re-check fast-path for inner elements if isinstance(a, (int, float, str, bool, type(None), bytes, bytearray, memoryview, range)): if a != b: return False continue # Mappings: keys must match; push value pairs if isinstance(a, Mapping): # Quick reject on size or key set mismatch if len(a) != len(b): # type: ignore[arg-type] return False # Using mapping view equality is O(n) and order-insensitive if a.keys() != b.keys(): # type: ignore[arg-type] return False # Compare corresponding values for k in a.keys(): stack.append((a[k], b[k])) # type: ignore[index] continue # Sets (unordered) if isinstance(a, Set) and not isinstance(a, (str, bytes, bytearray)): # Direct set equality is fine if a != b: # type: ignore[comparison-overlap] return False continue # Sequences (ordered) – but exclude string/bytes-like if isinstance(a, Sequence) and not isinstance(a, (str, bytes, bytearray)): if len(a) != len(b): # type: ignore[arg-type] return False # Push in order so mismatches are caught early # (iterate by index to avoid zip generator overhead) for i in range(len(a)): # type: ignore[arg-type] stack.append((a[i], b[i])) # type: ignore[index] continue # Fallback for everything else (custom objects, etc.) if a != b: return False return True