aboutsummaryrefslogtreecommitdiff
path: root/src/lib
diff options
context:
space:
mode:
authorFuwn <[email protected]>2023-11-22 23:45:17 -0800
committerFuwn <[email protected]>2023-11-22 23:45:17 -0800
commitffde08d759becc928f2e4bfd283665b2d6be86d4 (patch)
tree673ce9c018714665b9a78f67c09b730bb91688c4 /src/lib
parentfeat(wrapped): adjust window length (diff)
downloaddue.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.ts71
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;
}