Check equality of two NamedTuples with order of the fields ignored

Suppose I have the following two instances of NamedTuple.

julia> a = (x=1, y=2)
(x = 1, y = 2)

julia> b = (y=2, x=1)
(y = 2, x = 1)

julia> a == b
false

What would be the best approach to test whether a and b contain the same key-value pairs while ignoring the order of the pairs? (So, I want something that returns true for comparing a and b.)

Thanks!

julia> a = (x=1, y=2)
(x = 1, y = 2)

julia> b = (y=2, x=1)
(y = 2, x = 1)

julia> ntd(t) = Dict(zip(keys(t), values(t)))
ntd (generic function with 1 method)

julia> ntd(a) == ntd(b)
true
1 Like
sortednames(nt::NamedTuple{N,T}) where {N,T} =
  Tuple(sort([N...]))

sortednt(nt::NamedTuple) =
  NamedTuple{sortednames(nt)}(nt)

sortednt(a) == sortednt(b)
3 Likes
julia> all(k->getfield(a,k) == getfield(b,k), keys(a))
true
2 Likes

works nicely where

length(a) == length(allnames(a,b))
# and
allnames(a,b) = 
  Tuple(union(fieldnames(typeof(a)), fieldnames(typeof(b)))) 
β‰Š(x::NamedTuple{N,T}, y::NamedTuple{N2,T2}) where {N,T,N2,T2} =
  length(N) === length(union(N,N2)) &&
  all(k->getfield(x,k) == getfield(y,k), keys(x))

a = (x=1, y=2)
b = (y=2, x=1)

a β‰Š b # true
4 Likes

Those solution do not seem to handle recursive namedtuple, eg.

aa = (qux = (quuux = 4, quux = 3), foo = (bar = 1, baz = 2))
bb = (foo = (bar = 1, baz = 2), qux = (quuux = 4, quux = 3))

This is more of a bug in Base IMO. (may need a @pure sort(::Tuple) to dispatch statically)

Fix of improper previous counterexample

β‰Š(x::NamedTuple{N,T}, y::NamedTuple{N2,T2}) where {N,T,N2,T2} =
  length(N) === length(union(N,N2)) &&
  all(k->getfield(x,k) == getfield(y,k), keys(x))

using Test
aa = (; foo=(bar=1, baz=2), qux=(quux=3, quuux=4))
bb = (; qux=(quuux=4, quux=3), foo=(baz=2, bar=1))
@test_broken aa β‰Š bb

Let’s consider

Base.isapprox(x::NamedTuple{N,T}, y::NamedTuple{N2,T2}) where {N,T,N2,T2} =
    length(N) === length(union(N,N2)) &&
    all(keys(x)) do k
        isapprox(getfield(x,k), getfield(y,k))
    end

using Test
aa = (; foo=(bar=1, baz=2), qux=(quux=3, quuux=4))
bb = (; qux=(quuux=4, quux=3), foo=(baz=2, bar=1))
@test aa β‰ˆ bb

# may be too much side effect
aa = (; foo=(bar=(1+1e-8), baz=2), qux=(quux=3, quuux=4))
bb = (; qux=(quuux=4, quux=3), foo=(baz=2, bar=(1+1e-9)))
@test aa β‰ˆ bb

This may work / or not, considering wheter if we intend to recursively compare approximatively or not

Still a badly defined semantic from base IMO . Accepting eq upon perms is orthogonal to num tolerance