Skip to content

Commit d863bea

Browse files
committed
feat: levenshtein distance match
1 parent fe1490f commit d863bea

File tree

3 files changed

+40
-3
lines changed

3 files changed

+40
-3
lines changed

Diff for: package.json

+1
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
"class-variance-authority": "^0.7.0",
2525
"clipboard-polyfill": "^4.0.2",
2626
"clsx": "^2.1.0",
27+
"fastest-levenshtein": "^1.0.16",
2728
"ipx": "^2.0.0",
2829
"radix-vue": "^1.4.0",
2930
"tailwind-merge": "^2.2.1",

Diff for: pnpm-lock.yaml

+8
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Diff for: server/api/bgm/[input].ts

+31-3
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { BgmClient, type Search } from 'bgmc';
2+
import { distance } from 'fastest-levenshtein';
23

34
const client = new BgmClient(fetch);
45

@@ -17,7 +18,7 @@ export default defineEventHandler(async (event) => {
1718

1819
if (resp.list && resp.list.length > 0) {
1920
// 获取第一个搜索结果
20-
const foundId = await inferSubject(resp.list);
21+
const foundId = await inferSubject(input, resp.list);
2122

2223
if (foundId) {
2324
const [subject, persons] = await Promise.all([
@@ -47,13 +48,40 @@ export default defineEventHandler(async (event) => {
4748
return { subject: null, persons: null };
4849
});
4950

51+
/**
52+
* 使用编辑距离 (levenshtein distance),选择一个标题最相似的 subject
53+
*/
54+
function selectSubject(input: string, subjects: Search['list']) {
55+
const result1 = closest(input, subjects!, (subject) => subject.name);
56+
const result2 = closest(input, subjects!, (subject) => subject.name_cn);
57+
58+
return distance(input, result1.name ?? '') <= distance(input, result2.name_cn ?? '')
59+
? result1
60+
: result2;
61+
62+
function closest<T>(str: string, arr: T[], key: (value: T) => string | null | undefined) {
63+
let min_distance = Infinity;
64+
let min_index = 0;
65+
for (let i = 0; i < arr.length; i++) {
66+
const name = key(arr[i]);
67+
if (name === undefined || name === null) continue;
68+
const dist = distance(str, name);
69+
if (dist < min_distance) {
70+
min_distance = dist;
71+
min_index = i;
72+
}
73+
}
74+
return arr[min_index];
75+
}
76+
}
77+
5078
/**
5179
* 1. 使用搜索结果的第一个
5280
* 2. 如果它是系列中的某一部,则使用系列
5381
*/
54-
async function inferSubject(subjects: Search['list']) {
82+
async function inferSubject(input: string, subjects: Search['list']) {
5583
if (!subjects) return undefined;
56-
const first = subjects[0];
84+
const first = selectSubject(input, subjects);
5785
if (!first || !first.id) return undefined;
5886

5987
const related = await client.subjectRelated(first.id);

0 commit comments

Comments
 (0)