-
Notifications
You must be signed in to change notification settings - Fork 34
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
循環構造をサポートするdeep-equal関数 #460
Open
marihachi
wants to merge
17
commits into
master
Choose a base branch
from
work/deep-equal
base: master
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
17 commits
Select commit
Hold shift + click to select a range
e91af8d
add equal utility
marihachi 862471b
fix
marihachi 881ce38
add test
marihachi 3a42e85
update test
marihachi 0e7488d
rename
marihachi 694e301
refactor
marihachi 0c7105e
refactor
marihachi 45aa9cc
Merge branch 'master' into work/deep-equal
marihachi 390aea4
refactor
marihachi d622c7a
test
marihachi 634cc26
fix
marihachi 03da1a6
test
marihachi 5b3415e
fix
marihachi e2119e6
Merge branch 'master' into work/deep-equal
marihachi d4c00bc
add test
marihachi 1c06635
Merge branch 'master' into work/deep-equal
marihachi e33b7f1
update test
marihachi File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,71 @@ | ||
export function deepEqual(a: unknown, b: unknown): boolean { | ||
return deepEqualRefs(a, b, [], []); | ||
} | ||
|
||
function deepEqualRefs(a: unknown, b: unknown, refsA: unknown[], refsB: unknown[]): boolean { | ||
// プリミティブ値や参照の比較 | ||
// NOTE: Object.is()はNaN同士の比較でもtrue | ||
if (Object.is(a, b)) return true; | ||
|
||
// Object (a、b共にnullは含まない) | ||
if (a !== null && b !== null && typeof a === 'object' && typeof b === 'object') { | ||
// 参照の循環をチェック | ||
// 両方の循環が確認された時点で、その先も一致すると保証できるためtrueで返す | ||
const indexA = refsA.findIndex(x => x === a); | ||
const indexB = refsB.findIndex(x => x === b); | ||
if (indexA !== -1 && indexB !== -1) { | ||
return true; | ||
} | ||
|
||
// 次の参照パスを生成 | ||
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], nextRefsA, nextRefsB)) 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(); | ||
for (let i = 0; i < a.size; i++) { | ||
const entryA = aEntries.next(); | ||
const entryB = bEntries.next(); | ||
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 | ||
if (a instanceof Set && b instanceof Set) { | ||
if (a.size !== b.size) return false; | ||
const aValues = a.values(); | ||
const bValues = b.values(); | ||
for (let i = 0; i < a.size; i++) { | ||
const valueA = aValues.next(); | ||
const valueB = bValues.next(); | ||
if (!deepEqualRefs(valueA.value, valueB.value, nextRefsA, nextRefsB)) return false; | ||
} | ||
return true; | ||
} | ||
|
||
// object 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<string, unknown>)[key], (b as Record<string, unknown>)[key], nextRefsA, nextRefsB)) return false; | ||
} | ||
return true; | ||
} | ||
|
||
return false; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,92 @@ | ||
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); | ||
assert.strictEqual(deepEqual({ a: 1 }, { a: 1, b: 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('object', () => { | ||
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); | ||
}); | ||
|
||
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.a; | ||
assert.strictEqual(deepEqual(x, y), true); | ||
}); | ||
|
||
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; | ||
b[0].a[0] = b[0]; | ||
assert.strictEqual(deepEqual(a, b), false); | ||
}); | ||
}); |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
愚直にやるなら、indexAとindexBが同時に見つかるのが二回確認できればその先の一致が保証できると思います(一回だとループの開始位置にズレがある場合があるが、二回なら一回目のループ終了地点で開始を固定できる)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
aとbで同じ位置で循環が検出される場合は良いと思っています。
aとbでそれぞれ別の位置を起点として循環が現れるが、パターンとしては一致しているor一致してないというのを判定する処理が必要な気がしています。
具体的な処理は思いついていません。
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ん、2回まわすってことですか?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
はい。同位置で循環があった地点からもう一度回せば、「aとbで同じ位置で循環が検出される場合」に持ち込めるのでうまくいくはずです。(だいぶ効率が悪いとは思いますが)