Hi, Racket Discourse!
Just a quick one today: Have any of you come across a "clever" way to check for unordered list equality?
I've come up with this (low-effort) snippet, so far:
(define (and-reduce f tree-a tree-b)
(cond [(and (pair? tree-a) (pair? tree-b))
(and (f (car tree-a) (car tree-b))
(and-reduce f (cdr tree-a) (cdr tree-b)))]
[else (equal? tree-a tree-b)]))
(define (congruent? tree-a tree-b)
(cond [(and (pair? tree-a) (pair? tree-b))
(for/or ([permuted (in-permutations tree-b)])
(and-reduce congruent? tree-a permuted))]
[else (equal? tree-a tree-b)]))
(congruent? '(1 (a (4 3) (a c)) ((((2)) ((3)))))
'(1 ((((2)) ((3)))) (a (4 3) (a c))))
; => #true
(congruent? '(1 2 3 4)
'(4 2 3 1))
; => #true
(congruent? '((1) 2 (3))
'(2 (1) ((3))))
; => #false
It's not bad, but I feel like it is a very "brute-force" kind of solution, especially with the permutations.
The idea is to eventually abstract this out into a macro-like form that I can use for expressions like this:
(≡app
(exp (log a b) (log a))
≡ [distribute:spread]
(exp (log a) × 2) ; => (exp (log a) (log a))
(exp (log b) (log a)))
where the parenthesized expressions being matched and transformed are basically "typed bags", in the sense of each bag having a "head", e.g., exp
and log
.
I thought also of perhaps using the "traces" of the lists, e.g., for something like:
'(1 (1 2) 3 4 ((4)))
=> (1), ((1)), ((2)), (3), (4), (((4)))
and then comparing these "traces" between lists.
Potato, potato?
Edit for posterity: I realize now that my question is not very clear. Although I ostensibly enquire about a "clever way" to test between unordered lists for equality, I think it might come down to more of a "what is a relatively ergonomic structure which allows for the behaviour of multisets" question.
My thinking was that if I could find a "clever" way of thinking about equality (of multiset-like things), then I could generalize that to obtain what I desired.