fix: compare Set and Map entries without relying on order - #113
Conversation
Sorting entries cannot order objects, since they all stringify to the same value, so comparison fell back to insertion order. Pair each left entry with an unclaimed right entry instead.
Array#sort stringifies entries, and null-prototype objects cannot be converted to primitives, so entriesEqual threw a TypeError. Pairing entries without sorting fixes it; this locks the behavior.
|
While going through open issues I noticed this also fixes #51. The TypeError there comes from the same Pushed one more commit locking that with a regression test using the duck/cat null-prototype shape from the issue. 177 tests passing. |
Adds an 'unordered' parameter instead of a separate loop in entriesEqual, and claims matches by swapping in the last remaining entry rather than scanning past ones already claimed.
|
Done both. I took your swap-and-pop as written rather than my 177 tests pass and |
| var remaining = rightHandOperand.slice(); | ||
| var leftIndex = -1; | ||
| outer: | ||
| while (++leftIndex < length) { |
There was a problem hiding this comment.
any particular reason these are not just regular for loops?
| // Array#sort does not help here: every object entry stringifies to the same | ||
| // value, so the sort leaves them in insertion order. |
There was a problem hiding this comment.
we probably don't need to mention this as it doesn't mean anything to someone who doesn't know it used sort before. seems like leaking of thoughts
|
Both done. No good reason on the loops, I was matching the Agreed on the comment too. It only made sense as a diff against the 177 tests still pass and |
|
given this looks like agentic output, i'd like to see a response from the person driving the agent before we can land this. if this is the output of an agent (particularly the comments), let me know so i can better review this under that assumption. if it isn't, still let me know! |
|
i have only used agent for replies, all other code, changes are reviewed by me.. |
|
thank you 🙏 we get a lot of agentic noise these days so its very helpful to know there's a person behind it rather than it being fully automated. change looks good to me! |
Fixes #46 (the correctness half of it).
entriesEqualgathers Set/Map entries into arrays and compares them withiterableEqualafter a plain.sort(). That works for primitives, butArray#sortcompares by string, and every object entry stringifies to the same thing, so the sort leaves object entries in insertion order and the comparison becomes order-sensitive.Sets and Maps are unordered, so this reports unequal for collections that are equal:
The first case is exactly the repro @BridgeAR posted in #46 in 2017.
Approach
Rather than sorting, each left entry is paired with an as-yet unclaimed right entry. Deep equality is an equivalence relation, so if a left entry matches more than one right entry those right entries are themselves equal, which means taking the first match is safe and no backtracking is needed.
Size and empty-collection early-outs are untouched, so the common unequal-size case still exits immediately. Worst case is O(n²) comparisons where it was O(n log n) string comparisons before, but the previous ordering was only correct for primitives, and #46 covers the performance side separately.
Verification
index.jsalone and re-ran: 175 passing, 1 failing. So the test fails without the fix and passes with it.npm run lintreports no issues on these changes. (My Windows checkout produces a wall oflinebreak-styleCRLF errors across every file; git stores LF, and those are unrelated to this diff.)Behaviour I checked by hand, all as expected:
Set([{}, {a:5}])vsSet([{a:5}, {}])Set([{a:1},{b:2}])vsSet([{b:2},{a:1}])Set([1,2,3])vsSet([3,2,1])Map([['a',1],['b',2]])vs reversedSet([{a:1}])vsSet([{a:2}])Happy to close this if someone is already working on #46, or to split the performance concerns into a separate change.