diff options
| author | Fuwn <[email protected]> | 2023-11-22 23:45:17 -0800 |
|---|---|---|
| committer | Fuwn <[email protected]> | 2023-11-22 23:45:17 -0800 |
| commit | ffde08d759becc928f2e4bfd283665b2d6be86d4 (patch) | |
| tree | 673ce9c018714665b9a78f67c09b730bb91688c4 /src/lib | |
| parent | feat(wrapped): adjust window length (diff) | |
| download | due.moe-ffde08d759becc928f2e4bfd283665b2d6be86d4.tar.xz due.moe-ffde08d759becc928f2e4bfd283665b2d6be86d4.zip | |
feat(manga): new estimation methods
Diffstat (limited to 'src/lib')
| -rw-r--r-- | src/lib/AniList/media.ts | 71 |
1 files changed, 52 insertions, 19 deletions
diff --git a/src/lib/AniList/media.ts b/src/lib/AniList/media.ts index 8cc9c9c8..1ae33f7c 100644 --- a/src/lib/AniList/media.ts +++ b/src/lib/AniList/media.ts @@ -196,10 +196,45 @@ export const publicMediaListCollection = async (userId: number, type: Type): Pro return flattenLists(userIdResponse['data']['MediaListCollection']['lists']); }; +const countMedian = (guesses: number[]) => { + guesses.sort((a: number, b: number) => a - b); + + const mid = Math.floor(guesses.length / 2); + + return guesses.length % 2 !== 0 ? guesses[mid] : (guesses[mid - 1] + guesses[mid]) / 2; +}; + +const countIQR = (guesses: number[]) => { + guesses.sort((a: number, b: number) => a - b); + + const q1 = guesses[Math.floor(guesses.length / 4)]; + const q3 = guesses[Math.ceil(guesses.length * (3 / 4))]; + const iqr = q3 - q1; + + return guesses.filter((guess: number) => guess >= q1 - 1.5 * iqr && guess <= q3 + 1.5 * iqr); +}; + +const countMode = (guesses: number[]) => { + const frequency: { [key: number]: number } = {}; + let maximumFrequency = 0; + let mode = 0; + + for (const guess of guesses) { + frequency[guess] = (frequency[guess] || 0) + 1; + + if (frequency[guess] > maximumFrequency) { + maximumFrequency = frequency[guess]; + mode = guess; + } + } + + return mode; +}; + export const recentMediaActivities = async ( userIdentity: UserIdentity, media: Media, - method: 'median' | 'trimmed_mean' | 'weighted_average' = 'trimmed_mean' + method: 'median' | 'iqr_median' | 'iqr_mode' | 'mode' ): Promise<number | null> => { const activities = await ( await fetch('https://graphql.anilist.co', { @@ -235,34 +270,32 @@ export const recentMediaActivities = async ( guesses.sort((a, b) => b - a); if (guesses.length) { - let bestGuess = guesses[0]; + let bestGuess; switch (method) { case 'median': { - const sortedGuesses = guesses.slice().sort((a, b) => a - b); - - bestGuess = sortedGuesses[Math.floor(sortedGuesses.length / 2)]; + bestGuess = countMedian(guesses); + } + break; + case 'iqr_median': + { + bestGuess = countMedian(countIQR(guesses)); + } + break; + case 'iqr_mode': + { + bestGuess = countMode(countIQR(guesses)); } break; - case 'trimmed_mean': + case 'mode': { - const sortedGuesses = guesses.slice().sort((a, b) => a - b); - const trimmedGuesses = sortedGuesses.slice(1, -1); - bestGuess = trimmedGuesses.reduce((a, b) => a + b) / trimmedGuesses.length; + bestGuess = countMode(guesses); } break; - case 'weighted_average': + default: { - const countMap: { [key: number]: number } = {}; - guesses.forEach((g) => { - countMap[g] = (countMap[g] || 0) + 1; - }); - - bestGuess = - Object.entries(countMap).reduce((acc: number, [val, count]: [string, number]) => { - return acc + Number(val) * Math.log(count); - }, 0) / Object.values(countMap).reduce((a: number, b: number) => a + Math.log(b), 0); + bestGuess = countMedian(guesses); } break; } |