Files
aniplay-api/src/libs/findBestMatchingTitle.ts
Rushil Perera d680c97bc6 Removes console logs from title matching
Cleans up the code by removing console log statements used for debugging during the title matching process. This improves code readability and avoids unnecessary logging in the production environment.
2025-04-23 10:21:26 -04:00

140 lines
3.6 KiB
TypeScript

function findBestMatch(mainString: string, targets: string[]): string | null {
if (targets.length === 0) {
return null;
}
if (targets.length === 1) {
return targets[0];
}
let bestMatch = targets[0];
let highestScore = stringMatchingAlgorithm(mainString, bestMatch);
for (let i = 1; i < targets.length; i++) {
const currentScore = stringMatchingAlgorithm(mainString, targets[i]);
if (currentScore === 1) {
bestMatch = targets[i];
break;
}
if (currentScore > highestScore) {
highestScore = currentScore;
bestMatch = targets[i];
}
}
return bestMatch;
}
type Title = {
english: string;
userPreferred: string;
};
export const findBestMatchingTitle = (
title: Partial<Title>,
titlesToSearch: Partial<Title>[],
): { title: string | null; score: number } => {
const { english, userPreferred } = title;
let match;
let expectedSuffix = "";
if ((match = english?.match(/my hero academia.+([0-9])$/))) {
expectedSuffix = match[1];
}
const userPreferredBestMatch = userPreferred
? findBestMatch(
userPreferred,
titlesToSearch
.map((title) => title.userPreferred?.toLowerCase())
.filter(
(title) =>
title !== undefined &&
(!expectedSuffix || title.endsWith(expectedSuffix)),
),
)
: null;
const englishBestMatch = english
? findBestMatch(
english,
titlesToSearch
.map((title) => title.english?.toLowerCase())
.filter(
(title) =>
title !== undefined &&
(!expectedSuffix || title.endsWith(expectedSuffix)),
),
)
: null;
const userPreferredScore = userPreferredBestMatch
? stringMatchingAlgorithm(userPreferred!, userPreferredBestMatch)
: 0;
const englishScore = englishBestMatch
? stringMatchingAlgorithm(english!, englishBestMatch)
: 0;
console.log(title.english, englishScore);
console.log(title.userPreferred, userPreferredScore);
if (userPreferredScore >= englishScore) {
console.log("User preferred", userPreferredBestMatch);
return { title: userPreferredBestMatch, score: userPreferredScore };
} else {
console.log("English", englishBestMatch);
return { title: englishBestMatch, score: englishScore };
}
};
function stringMatchingAlgorithm(s1: string, s2: string): number {
// current implementation is the Jaro-Winkler algorithm
// copied from https://discord.com/channels/987492554486452315/1273988465222090783/1273988465222090783
const m = s1.length;
const n = s2.length;
if (m === 0 && n === 0) return 1.0;
if (m === 0 || n === 0) return 0.0;
const matchDistance = Math.floor(Math.max(m, n) / 2) - 1;
const s1Matches = new Array(m).fill(false);
const s2Matches = new Array(n).fill(false);
let matches = 0;
let transpositions = 0;
for (let i = 0; i < m; i++) {
const start = Math.max(0, i - matchDistance);
const end = Math.min(n - 1, i + matchDistance);
for (let j = start; j <= end; j++) {
if (s2Matches[j]) continue;
if (s1[i] !== s2[j]) continue;
s1Matches[i] = true;
s2Matches[j] = true;
matches++;
break;
}
}
if (matches === 0) return 0.0;
let k = 0;
for (let i = 0; i < m; i++) {
if (!s1Matches[i]) continue;
while (!s2Matches[k]) k++;
if (s1[i] !== s2[k]) transpositions++;
k++;
}
transpositions /= 2;
const jaro =
(matches / m + matches / n + (matches - transpositions) / matches) / 3;
const prefix = Math.min(
4,
[...s1].findIndex((ch, i) => s1[i] !== s2[i]),
);
const p = 0.1;
return jaro + prefix * p * (1 - jaro);
}