## Issue https://github.com/tadashi-aikawa/obsidian-another-quick-switcher/issues/102 ## 方針 `microFuzzy`の戻り値は今以下のとおり ```ts export type FuzzyResult = "starts-with" | "includes" | "fuzzy" | false; ``` ## ロジック ### 基本 - scoreSeedが存在する - 1文字ヒットすると+2 - N回連続してマッチすると`2^N` - 連続が途切れると2からやり直しで最終的には総和となる - score - scoreSeedを名称(ノートタイトル or alias)の長さで割る ### 採用ロジック ```ts export type FuzzyResult = | { type: "starts-with"; score: number } | { type: "includes"; score: number } | { type: "fuzzy"; score: number } | { type: "none"; score: number }; export function microFuzzy(value: string, query: string): FuzzyResult { if (value.startsWith(query)) { return { type: "starts-with", score: 2 ** query.length / value.length }; } if (value.includes(query)) { return { type: "includes", score: 2 ** query.length / value.length }; } let i = 0; let scoreSeed = 0; let combo = 0; for (let j = 0; j < value.length; j++) { if (value[j] === query[i]) { combo++; i++; } else { if (combo > 0) { scoreSeed += 2 ** combo; combo = 0; } } if (i === query.length) { if (combo > 0) { scoreSeed += 2 ** combo; } return { type: "fuzzy", score: scoreSeed / value.length }; } } return { type: "none", score: 0 }; } ``` ```ts describe.each<{ value: string; query: string; expected: FuzzyResult }>` value | query | expected ${"abcde"} | ${"ab"} | ${{ type: "starts-with", score: 0.8 }} ${"abcde"} | ${"bc"} | ${{ type: "includes", score: 0.8 }} ${"abcde"} | ${"ace"} | ${{ type: "fuzzy", score: 1.2 }} ${"abcde"} | ${"abcde"} | ${{ type: "starts-with", score: 6.4 }} ${"abcde"} | ${"abcdef"} | ${{ type: "none", score: 0 }} ${"abcde"} | ${"bd"} | ${{ type: "fuzzy", score: 0.8 }} ${"abcde"} | ${"ba"} | ${{ type: "none", score: 0 }} ${"fuzzy name match"} | ${"match"} | ${{ type: "includes", score: 2 }} `("microFuzzy", ({ value, query, expected }) => { test(`microFuzzy(${value}, ${query}) = ${expected}`, () => { expect(microFuzzy(value, query)).toStrictEqual(expected); }); }); ``` > [!question] 前方一致などは判定しないのか? > 1文字目だけヒットしてスコアが上がると予期せぬ挙動になりやすい。前方一致を優遇したい場合、[[🦉Another Quick Switcher]]の設定として[[Prefix name match]]を設定すべき。 ### ボツロジック `m match`が`match`に対して`fuzzy match`と見なされてしまう。期待値は`includes`。 ```ts export type FuzzyResult = | { type: "starts-with"; score: number } | { type: "includes"; score: number } | { type: "fuzzy"; score: number } | { type: "none"; score: number }; export function microFuzzy(value: string, query: string): FuzzyResult { let i = 0; let lastMatchIndex = null; let result: FuzzyResult = { type: "starts-with", score: 0 }; let scoreSeed = 0; let combo = 0; for (let j = 0; j < value.length; j++) { if (value[j] === query[i]) { if (lastMatchIndex == null) { if (j === 0) { result = { type: "starts-with", score: 0 }; } else { result = { type: "includes", score: 0 }; } combo = 1; } else if (j - lastMatchIndex > 1) { result = { type: "fuzzy", score: 0 }; combo++; } else { combo++; } lastMatchIndex = j; i++; } else { if (combo > 0) { scoreSeed += 2 ** combo; combo = 0; } } if (i === query.length) { if (combo > 0) { scoreSeed += 2 ** combo; } return { ...result, score: scoreSeed / value.length }; } } return { type: "none", score: 0 }; } ``` ```ts describe.each<{ value: string; query: string; expected: FuzzyResult }>` value | query | expected ${"abcde"} | ${"ab"} | ${{ type: "starts-with", score: 0.8 }} ${"abcde"} | ${"bc"} | ${{ type: "includes", score: 0.8 }} ${"abcde"} | ${"ace"} | ${{ type: "fuzzy", score: 1.2 }} ${"abcde"} | ${"abcde"} | ${{ type: "starts-with", score: 6.4 }} ${"abcde"} | ${"abcdef"} | ${{ type: "none", score: 0 }} ${"abcde"} | ${"bd"} | ${{ type: "fuzzy", score: 0.8 }} ${"abcde"} | ${"ba"} | ${{ type: "none", score: 0 }} `("microFuzzy", ({ value, query, expected }) => { test(`microFuzzy(${value}, ${query}) = ${expected}`, () => { expect(microFuzzy(value, query)).toStrictEqual(expected); }); }); ``` ## パフォーマンス 対応後。目立った劣化はなさそう。 ![[Pasted image 20230514125203.png]] ``` Prefix name match Alphabetical .md Last opened Last modified ```