From e91af8de66d2e350b0a3405b6123eb91bb8d499c Mon Sep 17 00:00:00 2001 From: marihachi Date: Tue, 7 Nov 2023 16:23:00 +0900 Subject: [PATCH 01/14] add equal utility --- src/equal.ts | 67 +++++++++++++++++++++++++++++++++++++++++++++++++++ test/equal.ts | 21 ++++++++++++++++ 2 files changed, 88 insertions(+) create mode 100644 src/equal.ts create mode 100644 test/equal.ts diff --git a/src/equal.ts b/src/equal.ts new file mode 100644 index 00000000..9d818ab4 --- /dev/null +++ b/src/equal.ts @@ -0,0 +1,67 @@ +export function equal(a: any, b: any): boolean { + return equalWithRefs(a, b, [], []); +} + +function equalWithRefs(a: any, b: any, prevRefsA: any[], prevRefsB: any[]): boolean { + if (Object.is(a, b)) return true; + + // object + if (a !== null && b !== null && typeof a === 'object' && typeof b === 'object') { + const refsA = [...prevRefsA, a]; + const refsB = [...prevRefsB, b]; + + // 循環チェック + const indexA = refsA.findIndex(x => x === a); + const indexB = refsB.findIndex(x => x === b); + if (indexA != -1 && indexB != -1 && indexA === indexB) { + return true; + } + + // array + if (Array.isArray(a) && Array.isArray(b)) { + if (a.length !== b.length) return false; + for (let i = 0; i < a.length; i++) { + if (!equalWithRefs(a[i], b[i], refsA, refsB)) return false; + } + return true; + } + + // map + if (a instanceof Map && b instanceof Map) { + if (a.size !== b.size) return false; + const aEntries = a.entries(); + const bEntries = b.entries(); + while (true) { + const entryA = aEntries.next(); + const entryB = bEntries.next(); + if (entryA.done === false) break; + if (!equalWithRefs(entryA.value[0], entryB.value[0], refsA, refsB)) return false; + if (!equalWithRefs(entryA.value[1], entryB.value[1], refsA, refsB)) return false; + } + return true; + } + + // set + if (a instanceof Set && b instanceof Set) { + if (a.size !== b.size) return false; + const aValues = a.values(); + const bValues = b.values(); + while (true) { + const valueA = aValues.next(); + const valueB = bValues.next(); + if (valueA.done === false) break; + if (!equalWithRefs(valueA.value, valueB.value, refsA, refsB)) return false; + } + return true; + } + + // object keys + const keys = Object.keys(a); + for (const key of keys) { + if (!equalWithRefs(a[key], b[key], refsA, refsB)) return false; + } + return true; + } + + return false; +} diff --git a/test/equal.ts b/test/equal.ts new file mode 100644 index 00000000..3d006333 --- /dev/null +++ b/test/equal.ts @@ -0,0 +1,21 @@ +import * as assert from 'assert'; +import { equal } from '../src/equal'; + +test('all', () => { + assert.strictEqual(equal(null, null), true); + assert.strictEqual(equal(undefined, undefined), true); + assert.strictEqual(equal(NaN, NaN), true); + + assert.strictEqual(equal(null, undefined), false); + assert.strictEqual(equal(null, NaN), false); + assert.strictEqual(equal(undefined, NaN), false); + + assert.strictEqual(equal({ a: 1 }, { a: 1 }), true); + assert.strictEqual(equal({ a: 1 }, null), false); + + let x: any = { n: null }; + x.n = x; + let y: any = { n: null }; + y.n = y; + assert.strictEqual(equal(x, y), true); +}); From 862471b7f23d09e21eeee0c6c67436b4f25d0d0f Mon Sep 17 00:00:00 2001 From: marihachi Date: Tue, 7 Nov 2023 21:03:01 +0900 Subject: [PATCH 02/14] fix --- src/equal.ts | 10 ++++------ test/equal.ts | 14 +++++++++++++- 2 files changed, 17 insertions(+), 7 deletions(-) diff --git a/src/equal.ts b/src/equal.ts index 9d818ab4..d0acb256 100644 --- a/src/equal.ts +++ b/src/equal.ts @@ -11,8 +11,8 @@ function equalWithRefs(a: any, b: any, prevRefsA: any[], prevRefsB: any[]): bool const refsB = [...prevRefsB, b]; // 循環チェック - const indexA = refsA.findIndex(x => x === a); - const indexB = refsB.findIndex(x => x === b); + const indexA = prevRefsA.findIndex(x => x === a); + const indexB = prevRefsB.findIndex(x => x === b); if (indexA != -1 && indexB != -1 && indexA === indexB) { return true; } @@ -31,10 +31,9 @@ function equalWithRefs(a: any, b: any, prevRefsA: any[], prevRefsB: any[]): bool if (a.size !== b.size) return false; const aEntries = a.entries(); const bEntries = b.entries(); - while (true) { + for (let i = 0; i < a.size; i++) { const entryA = aEntries.next(); const entryB = bEntries.next(); - if (entryA.done === false) break; if (!equalWithRefs(entryA.value[0], entryB.value[0], refsA, refsB)) return false; if (!equalWithRefs(entryA.value[1], entryB.value[1], refsA, refsB)) return false; } @@ -46,10 +45,9 @@ function equalWithRefs(a: any, b: any, prevRefsA: any[], prevRefsB: any[]): bool if (a.size !== b.size) return false; const aValues = a.values(); const bValues = b.values(); - while (true) { + for (let i = 0; i < a.size; i++) { const valueA = aValues.next(); const valueB = bValues.next(); - if (valueA.done === false) break; if (!equalWithRefs(valueA.value, valueB.value, refsA, refsB)) return false; } return true; diff --git a/test/equal.ts b/test/equal.ts index 3d006333..24b01470 100644 --- a/test/equal.ts +++ b/test/equal.ts @@ -1,7 +1,7 @@ import * as assert from 'assert'; import { equal } from '../src/equal'; -test('all', () => { +test('null, undefined, NaN', () => { assert.strictEqual(equal(null, null), true); assert.strictEqual(equal(undefined, undefined), true); assert.strictEqual(equal(NaN, NaN), true); @@ -9,13 +9,25 @@ test('all', () => { assert.strictEqual(equal(null, undefined), false); assert.strictEqual(equal(null, NaN), false); assert.strictEqual(equal(undefined, NaN), false); +}); +test('object', () => { assert.strictEqual(equal({ a: 1 }, { a: 1 }), true); assert.strictEqual(equal({ a: 1 }, null), false); +}); +test('recursive', () => { let x: any = { n: null }; x.n = x; let y: any = { n: null }; y.n = y; assert.strictEqual(equal(x, y), true); }); + +test('recursive 2', () => { + let x: any = { a: { b: { a: null } } }; + x.a.b.a = x.a; + let y: any = { a: { b: null } }; + y.a.b = y; + assert.strictEqual(equal(x, y), true); +}); From 881ce389c20b46fd6615afd5465d30b27b9c130b Mon Sep 17 00:00:00 2001 From: marihachi Date: Tue, 7 Nov 2023 21:06:44 +0900 Subject: [PATCH 03/14] add test --- test/equal.ts | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/test/equal.ts b/test/equal.ts index 24b01470..3ce648fd 100644 --- a/test/equal.ts +++ b/test/equal.ts @@ -16,6 +16,16 @@ test('object', () => { assert.strictEqual(equal({ a: 1 }, null), false); }); +test('number', () => { + assert.strictEqual(equal(1, 1), true); + assert.strictEqual(equal(1, 2), false); +}); + +test('array', () => { + assert.strictEqual(equal([1], [1]), true); + assert.strictEqual(equal([1], [2]), false); +}); + test('recursive', () => { let x: any = { n: null }; x.n = x; From 3a42e856c6f55e14ba16a1a4483ec106aaf079c3 Mon Sep 17 00:00:00 2001 From: marihachi Date: Tue, 7 Nov 2023 21:30:15 +0900 Subject: [PATCH 04/14] update test --- test/equal.ts | 74 +++++++++++++++++++++++++++++++-------------------- 1 file changed, 45 insertions(+), 29 deletions(-) diff --git a/test/equal.ts b/test/equal.ts index 3ce648fd..4bb1cb18 100644 --- a/test/equal.ts +++ b/test/equal.ts @@ -1,43 +1,59 @@ import * as assert from 'assert'; import { equal } from '../src/equal'; +describe('compare', () => { + test('object and object', () => { + assert.strictEqual(equal({ a: 1 }, { a: 1 }), true); + assert.strictEqual(equal({ a: 1 }, { a: 2 }), false); + }); + + test('number and number', () => { + assert.strictEqual(equal(1, 1), true); + assert.strictEqual(equal(1, 2), false); + }); + + test('number[] and number[]', () => { + assert.strictEqual(equal([1, 2, 3], [1, 2, 3]), true); + assert.strictEqual(equal([1, 2, 3], [4, 5, 6]), false); + assert.strictEqual(equal([1, 2], [1, 2, 3]), false); + assert.strictEqual(equal([1, 2, 3], [1, 2]), false); + }); + + test('string[] and string[]', () => { + assert.strictEqual(equal(['a', 'b', 'c'], ['a', 'b', 'c']), true); + assert.strictEqual(equal(['a', 'b', 'c'], ['x', 'y', 'z']), false); + assert.strictEqual(equal(['a', 'b'], ['a', 'b', 'c']), false); + assert.strictEqual(equal(['a', 'b', 'c'], ['a', 'b']), false); + }); + + test('object and null', () => { + assert.strictEqual(equal({ a: 1 }, null), false); + }); +}); + test('null, undefined, NaN', () => { assert.strictEqual(equal(null, null), true); assert.strictEqual(equal(undefined, undefined), true); assert.strictEqual(equal(NaN, NaN), true); - assert.strictEqual(equal(null, undefined), false); assert.strictEqual(equal(null, NaN), false); assert.strictEqual(equal(undefined, NaN), false); }); -test('object', () => { - assert.strictEqual(equal({ a: 1 }, { a: 1 }), true); - assert.strictEqual(equal({ a: 1 }, null), false); -}); - -test('number', () => { - assert.strictEqual(equal(1, 1), true); - assert.strictEqual(equal(1, 2), false); -}); - -test('array', () => { - assert.strictEqual(equal([1], [1]), true); - assert.strictEqual(equal([1], [2]), false); -}); - -test('recursive', () => { - let x: any = { n: null }; - x.n = x; - let y: any = { n: null }; - y.n = y; - assert.strictEqual(equal(x, y), true); -}); +describe('recursive', () => { + test('simple', () => { + let x: any = { n: null }; + x.n = x; + let y: any = { n: null }; + y.n = y; + assert.strictEqual(equal(x, y), true); + }); -test('recursive 2', () => { - let x: any = { a: { b: { a: null } } }; - x.a.b.a = x.a; - let y: any = { a: { b: null } }; - y.a.b = y; - assert.strictEqual(equal(x, y), true); + test('complex', () => { + let x: any = { a: { b: { a: null } } }; + x.a.b.a = x.a; + let y: any = { a: { b: null } }; + y.a.b = y; + assert.strictEqual(equal(x, y), true); + }); }); From 0e7488d7d5882408a5d35cd737cec95d2d6cf9f2 Mon Sep 17 00:00:00 2001 From: marihachi Date: Tue, 7 Nov 2023 21:42:15 +0900 Subject: [PATCH 05/14] rename --- src/{equal.ts => interpreter/deep-equal.ts} | 18 +++---- test/deep-equal.ts | 59 +++++++++++++++++++++ test/equal.ts | 59 --------------------- 3 files changed, 68 insertions(+), 68 deletions(-) rename src/{equal.ts => interpreter/deep-equal.ts} (70%) create mode 100644 test/deep-equal.ts delete mode 100644 test/equal.ts diff --git a/src/equal.ts b/src/interpreter/deep-equal.ts similarity index 70% rename from src/equal.ts rename to src/interpreter/deep-equal.ts index d0acb256..65840e00 100644 --- a/src/equal.ts +++ b/src/interpreter/deep-equal.ts @@ -1,8 +1,8 @@ -export function equal(a: any, b: any): boolean { - return equalWithRefs(a, b, [], []); +export function deepEqual(a: any, b: any): boolean { + return deepEqualRefs(a, b, [], []); } -function equalWithRefs(a: any, b: any, prevRefsA: any[], prevRefsB: any[]): boolean { +function deepEqualRefs(a: any, b: any, prevRefsA: any[], prevRefsB: any[]): boolean { if (Object.is(a, b)) return true; // object @@ -13,7 +13,7 @@ function equalWithRefs(a: any, b: any, prevRefsA: any[], prevRefsB: any[]): bool // 循環チェック const indexA = prevRefsA.findIndex(x => x === a); const indexB = prevRefsB.findIndex(x => x === b); - if (indexA != -1 && indexB != -1 && indexA === indexB) { + if (indexA !== -1 && indexB !== -1 && indexA === indexB) { return true; } @@ -21,7 +21,7 @@ function equalWithRefs(a: any, b: any, prevRefsA: any[], prevRefsB: any[]): bool if (Array.isArray(a) && Array.isArray(b)) { if (a.length !== b.length) return false; for (let i = 0; i < a.length; i++) { - if (!equalWithRefs(a[i], b[i], refsA, refsB)) return false; + if (!deepEqualRefs(a[i], b[i], refsA, refsB)) return false; } return true; } @@ -34,8 +34,8 @@ function equalWithRefs(a: any, b: any, prevRefsA: any[], prevRefsB: any[]): bool for (let i = 0; i < a.size; i++) { const entryA = aEntries.next(); const entryB = bEntries.next(); - if (!equalWithRefs(entryA.value[0], entryB.value[0], refsA, refsB)) return false; - if (!equalWithRefs(entryA.value[1], entryB.value[1], refsA, refsB)) return false; + if (!deepEqualRefs(entryA.value[0], entryB.value[0], refsA, refsB)) return false; + if (!deepEqualRefs(entryA.value[1], entryB.value[1], refsA, refsB)) return false; } return true; } @@ -48,7 +48,7 @@ function equalWithRefs(a: any, b: any, prevRefsA: any[], prevRefsB: any[]): bool for (let i = 0; i < a.size; i++) { const valueA = aValues.next(); const valueB = bValues.next(); - if (!equalWithRefs(valueA.value, valueB.value, refsA, refsB)) return false; + if (!deepEqualRefs(valueA.value, valueB.value, refsA, refsB)) return false; } return true; } @@ -56,7 +56,7 @@ function equalWithRefs(a: any, b: any, prevRefsA: any[], prevRefsB: any[]): bool // object keys const keys = Object.keys(a); for (const key of keys) { - if (!equalWithRefs(a[key], b[key], refsA, refsB)) return false; + if (!deepEqualRefs(a[key], b[key], refsA, refsB)) return false; } return true; } diff --git a/test/deep-equal.ts b/test/deep-equal.ts new file mode 100644 index 00000000..c9b6f806 --- /dev/null +++ b/test/deep-equal.ts @@ -0,0 +1,59 @@ +import * as assert from 'assert'; +import { deepEqual } from '../src/interpreter/deep-equal'; + +describe('compare', () => { + test('object and object', () => { + assert.strictEqual(deepEqual({ a: 1 }, { a: 1 }), true); + assert.strictEqual(deepEqual({ a: 1 }, { a: 2 }), false); + }); + + test('number and number', () => { + assert.strictEqual(deepEqual(1, 1), true); + assert.strictEqual(deepEqual(1, 2), false); + }); + + test('number[] and number[]', () => { + assert.strictEqual(deepEqual([1, 2, 3], [1, 2, 3]), true); + assert.strictEqual(deepEqual([1, 2, 3], [4, 5, 6]), false); + assert.strictEqual(deepEqual([1, 2], [1, 2, 3]), false); + assert.strictEqual(deepEqual([1, 2, 3], [1, 2]), false); + }); + + test('string[] and string[]', () => { + assert.strictEqual(deepEqual(['a', 'b', 'c'], ['a', 'b', 'c']), true); + assert.strictEqual(deepEqual(['a', 'b', 'c'], ['x', 'y', 'z']), false); + assert.strictEqual(deepEqual(['a', 'b'], ['a', 'b', 'c']), false); + assert.strictEqual(deepEqual(['a', 'b', 'c'], ['a', 'b']), false); + }); + + test('object and null', () => { + assert.strictEqual(deepEqual({ a: 1 }, null), false); + }); +}); + +test('null, undefined, NaN', () => { + assert.strictEqual(deepEqual(null, null), true); + assert.strictEqual(deepEqual(undefined, undefined), true); + assert.strictEqual(deepEqual(NaN, NaN), true); + assert.strictEqual(deepEqual(null, undefined), false); + assert.strictEqual(deepEqual(null, NaN), false); + assert.strictEqual(deepEqual(undefined, NaN), false); +}); + +describe('recursive', () => { + test('simple', () => { + let x: any = { n: null }; + x.n = x; + let y: any = { n: null }; + y.n = y; + assert.strictEqual(deepEqual(x, y), true); + }); + + test('complex', () => { + let x: any = { a: { b: { a: null } } }; + x.a.b.a = x.a; + let y: any = { a: { b: null } }; + y.a.b = y; + assert.strictEqual(deepEqual(x, y), true); + }); +}); diff --git a/test/equal.ts b/test/equal.ts deleted file mode 100644 index 4bb1cb18..00000000 --- a/test/equal.ts +++ /dev/null @@ -1,59 +0,0 @@ -import * as assert from 'assert'; -import { equal } from '../src/equal'; - -describe('compare', () => { - test('object and object', () => { - assert.strictEqual(equal({ a: 1 }, { a: 1 }), true); - assert.strictEqual(equal({ a: 1 }, { a: 2 }), false); - }); - - test('number and number', () => { - assert.strictEqual(equal(1, 1), true); - assert.strictEqual(equal(1, 2), false); - }); - - test('number[] and number[]', () => { - assert.strictEqual(equal([1, 2, 3], [1, 2, 3]), true); - assert.strictEqual(equal([1, 2, 3], [4, 5, 6]), false); - assert.strictEqual(equal([1, 2], [1, 2, 3]), false); - assert.strictEqual(equal([1, 2, 3], [1, 2]), false); - }); - - test('string[] and string[]', () => { - assert.strictEqual(equal(['a', 'b', 'c'], ['a', 'b', 'c']), true); - assert.strictEqual(equal(['a', 'b', 'c'], ['x', 'y', 'z']), false); - assert.strictEqual(equal(['a', 'b'], ['a', 'b', 'c']), false); - assert.strictEqual(equal(['a', 'b', 'c'], ['a', 'b']), false); - }); - - test('object and null', () => { - assert.strictEqual(equal({ a: 1 }, null), false); - }); -}); - -test('null, undefined, NaN', () => { - assert.strictEqual(equal(null, null), true); - assert.strictEqual(equal(undefined, undefined), true); - assert.strictEqual(equal(NaN, NaN), true); - assert.strictEqual(equal(null, undefined), false); - assert.strictEqual(equal(null, NaN), false); - assert.strictEqual(equal(undefined, NaN), false); -}); - -describe('recursive', () => { - test('simple', () => { - let x: any = { n: null }; - x.n = x; - let y: any = { n: null }; - y.n = y; - assert.strictEqual(equal(x, y), true); - }); - - test('complex', () => { - let x: any = { a: { b: { a: null } } }; - x.a.b.a = x.a; - let y: any = { a: { b: null } }; - y.a.b = y; - assert.strictEqual(equal(x, y), true); - }); -}); From 694e301a2826682dacb133b9d6acba217bdd7ab6 Mon Sep 17 00:00:00 2001 From: marihachi Date: Tue, 7 Nov 2023 21:55:15 +0900 Subject: [PATCH 06/14] refactor --- src/interpreter/deep-equal.ts | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/interpreter/deep-equal.ts b/src/interpreter/deep-equal.ts index 65840e00..71782ef6 100644 --- a/src/interpreter/deep-equal.ts +++ b/src/interpreter/deep-equal.ts @@ -1,8 +1,8 @@ -export function deepEqual(a: any, b: any): boolean { +export function deepEqual(a: unknown, b: unknown): boolean { return deepEqualRefs(a, b, [], []); } -function deepEqualRefs(a: any, b: any, prevRefsA: any[], prevRefsB: any[]): boolean { +function deepEqualRefs(a: unknown, b: unknown, prevRefsA: unknown[], prevRefsB: unknown[]): boolean { if (Object.is(a, b)) return true; // object @@ -13,6 +13,7 @@ function deepEqualRefs(a: any, b: any, prevRefsA: any[], prevRefsB: any[]): bool // 循環チェック const indexA = prevRefsA.findIndex(x => x === a); const indexB = prevRefsB.findIndex(x => x === b); + // NOTE: indexA,Bの一致を確認しているが、これで良いのか怪しい if (indexA !== -1 && indexB !== -1 && indexA === indexB) { return true; } @@ -56,7 +57,7 @@ function deepEqualRefs(a: any, b: any, prevRefsA: any[], prevRefsB: any[]): bool // object keys const keys = Object.keys(a); for (const key of keys) { - if (!deepEqualRefs(a[key], b[key], refsA, refsB)) return false; + if (!deepEqualRefs((a as Record)[key], (b as Record)[key], refsA, refsB)) return false; } return true; } From 0c7105ee3ccaee54fcb49d8c313ebe3130bc7cb7 Mon Sep 17 00:00:00 2001 From: marihachi Date: Tue, 7 Nov 2023 22:04:14 +0900 Subject: [PATCH 07/14] refactor --- src/interpreter/deep-equal.ts | 34 ++++++++++++++++++---------------- 1 file changed, 18 insertions(+), 16 deletions(-) diff --git a/src/interpreter/deep-equal.ts b/src/interpreter/deep-equal.ts index 71782ef6..72e26848 100644 --- a/src/interpreter/deep-equal.ts +++ b/src/interpreter/deep-equal.ts @@ -2,32 +2,34 @@ export function deepEqual(a: unknown, b: unknown): boolean { return deepEqualRefs(a, b, [], []); } -function deepEqualRefs(a: unknown, b: unknown, prevRefsA: unknown[], prevRefsB: unknown[]): boolean { +function deepEqualRefs(a: unknown, b: unknown, refsA: unknown[], refsB: unknown[]): boolean { + // NOTE: Object.is()ではNaN同士の比較結果もtrue if (Object.is(a, b)) return true; - // object + // Object if (a !== null && b !== null && typeof a === 'object' && typeof b === 'object') { - const refsA = [...prevRefsA, a]; - const refsB = [...prevRefsB, b]; - - // 循環チェック - const indexA = prevRefsA.findIndex(x => x === a); - const indexB = prevRefsB.findIndex(x => x === b); + // 参照の循環をチェック // NOTE: indexA,Bの一致を確認しているが、これで良いのか怪しい + const indexA = refsA.findIndex(x => x === a); + const indexB = refsB.findIndex(x => x === b); if (indexA !== -1 && indexB !== -1 && indexA === indexB) { return true; } - // array + // 次の参照パスを生成 + const nextRefsA = [...refsA, a]; + const nextRefsB = [...refsB, b]; + + // Array if (Array.isArray(a) && Array.isArray(b)) { if (a.length !== b.length) return false; for (let i = 0; i < a.length; i++) { - if (!deepEqualRefs(a[i], b[i], refsA, refsB)) return false; + if (!deepEqualRefs(a[i], b[i], nextRefsA, nextRefsB)) return false; } return true; } - // map + // Map if (a instanceof Map && b instanceof Map) { if (a.size !== b.size) return false; const aEntries = a.entries(); @@ -35,13 +37,13 @@ function deepEqualRefs(a: unknown, b: unknown, prevRefsA: unknown[], prevRefsB: for (let i = 0; i < a.size; i++) { const entryA = aEntries.next(); const entryB = bEntries.next(); - if (!deepEqualRefs(entryA.value[0], entryB.value[0], refsA, refsB)) return false; - if (!deepEqualRefs(entryA.value[1], entryB.value[1], refsA, refsB)) return false; + if (!deepEqualRefs(entryA.value[0], entryB.value[0], nextRefsA, nextRefsB)) return false; + if (!deepEqualRefs(entryA.value[1], entryB.value[1], nextRefsA, nextRefsB)) return false; } return true; } - // set + // Set if (a instanceof Set && b instanceof Set) { if (a.size !== b.size) return false; const aValues = a.values(); @@ -49,7 +51,7 @@ function deepEqualRefs(a: unknown, b: unknown, prevRefsA: unknown[], prevRefsB: for (let i = 0; i < a.size; i++) { const valueA = aValues.next(); const valueB = bValues.next(); - if (!deepEqualRefs(valueA.value, valueB.value, refsA, refsB)) return false; + if (!deepEqualRefs(valueA.value, valueB.value, nextRefsA, nextRefsB)) return false; } return true; } @@ -57,7 +59,7 @@ function deepEqualRefs(a: unknown, b: unknown, prevRefsA: unknown[], prevRefsB: // object keys const keys = Object.keys(a); for (const key of keys) { - if (!deepEqualRefs((a as Record)[key], (b as Record)[key], refsA, refsB)) return false; + if (!deepEqualRefs((a as Record)[key], (b as Record)[key], nextRefsA, nextRefsB)) return false; } return true; } From 390aea439f9650eec903da460d84b0180472cafc Mon Sep 17 00:00:00 2001 From: marihachi Date: Tue, 7 Nov 2023 22:12:12 +0900 Subject: [PATCH 08/14] refactor --- src/interpreter/deep-equal.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/interpreter/deep-equal.ts b/src/interpreter/deep-equal.ts index 72e26848..1d1368cb 100644 --- a/src/interpreter/deep-equal.ts +++ b/src/interpreter/deep-equal.ts @@ -3,10 +3,11 @@ export function deepEqual(a: unknown, b: unknown): boolean { } function deepEqualRefs(a: unknown, b: unknown, refsA: unknown[], refsB: unknown[]): boolean { - // NOTE: Object.is()ではNaN同士の比較結果もtrue + // プリミティブ値や参照の比較 + // NOTE: Object.is()はNaN同士の比較でもtrue if (Object.is(a, b)) return true; - // Object + // Object (a、b共にnullは含まない) if (a !== null && b !== null && typeof a === 'object' && typeof b === 'object') { // 参照の循環をチェック // NOTE: indexA,Bの一致を確認しているが、これで良いのか怪しい From d622c7a5b188ed24025b5fc626639e203aa8df94 Mon Sep 17 00:00:00 2001 From: marihachi Date: Tue, 7 Nov 2023 22:33:35 +0900 Subject: [PATCH 09/14] test --- test/deep-equal.ts | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/test/deep-equal.ts b/test/deep-equal.ts index c9b6f806..238599bc 100644 --- a/test/deep-equal.ts +++ b/test/deep-equal.ts @@ -56,4 +56,12 @@ describe('recursive', () => { y.a.b = y; assert.strictEqual(deepEqual(x, y), true); }); + + test('complex 2', () => { + let x: any = { a: { b: null } }; + x.a.b = x; + let y: any = { a: { b: { a: { b: { a: null } } } } }; + y.a.b.a.b.a = y.a.b; + assert.strictEqual(deepEqual(x, y), true); + }); }); From 634cc26001a15ffdaba59243fb20ece26e4d89f2 Mon Sep 17 00:00:00 2001 From: marihachi Date: Tue, 7 Nov 2023 22:38:19 +0900 Subject: [PATCH 10/14] fix --- src/interpreter/deep-equal.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/interpreter/deep-equal.ts b/src/interpreter/deep-equal.ts index 1d1368cb..f37da75d 100644 --- a/src/interpreter/deep-equal.ts +++ b/src/interpreter/deep-equal.ts @@ -10,10 +10,10 @@ function deepEqualRefs(a: unknown, b: unknown, refsA: unknown[], refsB: unknown[ // Object (a、b共にnullは含まない) if (a !== null && b !== null && typeof a === 'object' && typeof b === 'object') { // 参照の循環をチェック - // NOTE: indexA,Bの一致を確認しているが、これで良いのか怪しい + // 両方の循環が確認された時点で、その先も一致すると保証できるためtrueで返す const indexA = refsA.findIndex(x => x === a); const indexB = refsB.findIndex(x => x === b); - if (indexA !== -1 && indexB !== -1 && indexA === indexB) { + if (indexA !== -1 && indexB !== -1) { return true; } From 03da1a6029cb7bb0f1aee2218c047575a36b02c3 Mon Sep 17 00:00:00 2001 From: marihachi Date: Tue, 7 Nov 2023 23:14:19 +0900 Subject: [PATCH 11/14] test --- test/deep-equal.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/test/deep-equal.ts b/test/deep-equal.ts index 238599bc..707fd866 100644 --- a/test/deep-equal.ts +++ b/test/deep-equal.ts @@ -5,6 +5,7 @@ describe('compare', () => { test('object and object', () => { assert.strictEqual(deepEqual({ a: 1 }, { a: 1 }), true); assert.strictEqual(deepEqual({ a: 1 }, { a: 2 }), false); + assert.strictEqual(deepEqual({ a: 1 }, { a: 1, b: 2 }), false); }); test('number and number', () => { From 5b3415e8de8ec21162167d9d319f906cfe5d724f Mon Sep 17 00:00:00 2001 From: marihachi Date: Tue, 7 Nov 2023 23:17:47 +0900 Subject: [PATCH 12/14] fix --- src/interpreter/deep-equal.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/interpreter/deep-equal.ts b/src/interpreter/deep-equal.ts index f37da75d..c3fc8b13 100644 --- a/src/interpreter/deep-equal.ts +++ b/src/interpreter/deep-equal.ts @@ -58,8 +58,10 @@ function deepEqualRefs(a: unknown, b: unknown, refsA: unknown[], refsB: unknown[ } // object keys - const keys = Object.keys(a); - for (const key of keys) { + const keysA = Object.keys(a); + const keysB = Object.keys(b); + if (keysA.length !== keysB.length) return false; + for (const key of keysA) { if (!deepEqualRefs((a as Record)[key], (b as Record)[key], nextRefsA, nextRefsB)) return false; } return true; From d4c00bca3edd07c3ff51a457d4cac8ece77fcacd Mon Sep 17 00:00:00 2001 From: marihachi Date: Thu, 14 Mar 2024 22:04:57 +0900 Subject: [PATCH 13/14] add test --- test/deep-equal.ts | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/test/deep-equal.ts b/test/deep-equal.ts index 707fd866..9a696765 100644 --- a/test/deep-equal.ts +++ b/test/deep-equal.ts @@ -65,4 +65,12 @@ describe('recursive', () => { y.a.b.a.b.a = y.a.b; assert.strictEqual(deepEqual(x, y), true); }); + + test('complex 3', () => { + let a: any = [{ a: [] }]; + let b: any = [{ a: [] }]; + a[0].a[0] = a; + b[0].a[0] = b[0]; + assert.strictEqual(deepEqual(a, b), false); + }); }); From e33b7f14bd4041e6be668ac362df27a0c5c55c6c Mon Sep 17 00:00:00 2001 From: marihachi Date: Sat, 16 Mar 2024 11:22:17 +0900 Subject: [PATCH 14/14] update test --- test/deep-equal.ts | 24 ++++++++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/test/deep-equal.ts b/test/deep-equal.ts index 9a696765..f84ee3b6 100644 --- a/test/deep-equal.ts +++ b/test/deep-equal.ts @@ -50,7 +50,7 @@ describe('recursive', () => { assert.strictEqual(deepEqual(x, y), true); }); - test('complex', () => { + test('object', () => { let x: any = { a: { b: { a: null } } }; x.a.b.a = x.a; let y: any = { a: { b: null } }; @@ -58,15 +58,31 @@ describe('recursive', () => { assert.strictEqual(deepEqual(x, y), true); }); - test('complex 2', () => { + test('object 2', () => { + let x: any = { a: { b: { a: null } } }; + x.a.b.a = x.a; + let y: any = { a: { b: null } }; + y.a.b = y.a; + assert.strictEqual(deepEqual(x, y), false); + }); + + test('different path of object', () => { let x: any = { a: { b: null } }; x.a.b = x; let y: any = { a: { b: { a: { b: { a: null } } } } }; - y.a.b.a.b.a = y.a.b; + y.a.b.a.b.a = y.a.b.a; assert.strictEqual(deepEqual(x, y), true); }); - test('complex 3', () => { + test('different path of object 2', () => { + let x: any = { a: { b: null } }; + x.a.b = x; + let y: any = { a: { b: { a: { b: { a: null } } } } }; + y.a.b.a.b.a = y.a.b; + assert.strictEqual(deepEqual(x, y), false); + }); + + test('object and array', () => { let a: any = [{ a: [] }]; let b: any = [{ a: [] }]; a[0].a[0] = a;