Skip to content

Commit

Permalink
chore: detach dices coefficient to be the must-use alg
Browse files Browse the repository at this point in the history
  • Loading branch information
JacksonVirgo committed Jan 8, 2024
1 parent 3fa4f50 commit 947425f
Show file tree
Hide file tree
Showing 3 changed files with 64 additions and 68 deletions.
18 changes: 7 additions & 11 deletions src/content/votecount.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { getThreadData } from './thread';
import $ from 'jquery';
import { isMemberVerificationResponse } from '../types/backgroundResponse';
import { sendBackgroundRequest } from './request';
import { findBestMatch } from '../utils/stringCorrection';
import { stringSimilarityAlgs } from '../utils/stringCorrection';

const CORRECTION_ACCEPT_THRESHOLD = 0.88;
const CORRECTION_WARN_THRESHOLD = 0.95;
Expand Down Expand Up @@ -75,8 +75,7 @@ export async function startVoteCount(gameDefinition: GameDefinition | null) {

// Check if author is dead
const isAuthorDead = false;
for (const [key, value] of Object.entries(gameDefinition?.dead ?? {}))
if (vote.author.toLowerCase() === key.toLowerCase() && value <= vote.post) return false;
for (const [key, value] of Object.entries(gameDefinition?.dead ?? {})) if (vote.author.toLowerCase() === key.toLowerCase() && value <= vote.post) return false;
if (isAuthorDead) return false;

return true;
Expand Down Expand Up @@ -117,14 +116,13 @@ export async function startVoteCount(gameDefinition: GameDefinition | null) {
totalVotables.push(UNVOTE_TAG);
if (!gameDefinition.disable?.includes('No Elimination')) totalVotables.push(NO_ELIMINATION_TAG);

const closestMatch = findBestMatch(vote.target.toLowerCase(), totalVotables).bestMatch;

let validatedName = closestMatch.target;
const closestMatch = stringSimilarityAlgs.dice_coefficient.bestMatch(vote.target.toLowerCase(), totalVotables).bestMatch;
let validatedName = closestMatch[0];
if (validatedName != UNVOTE_TAG && validatedName != NO_ELIMINATION_TAG) validatedName = aliasLegend.get(validatedName) ?? validatedName;
vote.target = validatedName;

if (closestMatch.rating >= CORRECTION_ACCEPT_THRESHOLD) vote.validity = VoteCorrection.ACCEPT;
else if (closestMatch.rating >= CORRECTION_WARN_THRESHOLD) vote.validity = VoteCorrection.WARN;
if (closestMatch[1] >= CORRECTION_ACCEPT_THRESHOLD) vote.validity = VoteCorrection.ACCEPT;
else if (closestMatch[1] >= CORRECTION_WARN_THRESHOLD) vote.validity = VoteCorrection.WARN;
else vote.validity = VoteCorrection.REJECT;
return vote;
})
Expand Down Expand Up @@ -216,9 +214,7 @@ export function formatVoteCountData(voteCount: VoteCount) {
const calculatedMajority = wagonHandle == NO_ELIMINATION_TAG ? Math.ceil(voteCount.livingPlayers.length / 2) : voteCount.majority;
const wagonLength = wagonHandle == NO_ELIMINATION_TAG ? -1 : wagon.length;
const wagonTitle = wagonHandle == NO_ELIMINATION_TAG ? 'No Elimination' : wagonHandle;
const wagonStr = `[b]${wagonTitle} (${wagon.length}/${calculatedMajority})[/b] -> ${wagon
.map((v) => `${v.author} ([post]${v.post}[/post])`)
.join(', ')}`;
const wagonStr = `[b]${wagonTitle} (${wagon.length}/${calculatedMajority})[/b] -> ${wagon.map((v) => `${v.author} ([post]${v.post}[/post])`).join(', ')}`;
wagonStrings.push([wagonStr, wagonLength]);
}

Expand Down
88 changes: 31 additions & 57 deletions src/utils/stringCorrection.ts
Original file line number Diff line number Diff line change
@@ -1,61 +1,35 @@
export function compareTwoStrings(first: string, second: string) {
first = first.replace(/\s+/g, '');
second = second.replace(/\s+/g, '');

if (first === second) return 1; // identical or empty
if (first.length < 2 || second.length < 2) return 0; // if either is a 0-letter or 1-letter string

const firstBigrams = new Map();
for (let i = 0; i < first.length - 1; i++) {
const bigram = first.substring(i, i + 2);
const count = firstBigrams.has(bigram) ? firstBigrams.get(bigram) + 1 : 1;

firstBigrams.set(bigram, count);
}

let intersectionSize = 0;
for (let i = 0; i < second.length - 1; i++) {
const bigram = second.substring(i, i + 2);
const count = firstBigrams.has(bigram) ? firstBigrams.get(bigram) : 0;

if (count > 0) {
firstBigrams.set(bigram, count - 1);
intersectionSize++;
}
}

return (2.0 * intersectionSize) / (first.length + second.length - 2);
import diceCoefficient from './stringSimilarity/dice';

export type StringSimilarityCompareFunc = (first: string, second: string) => number;
export type BestMatchResult = {
ratings: [string, number][];
bestMatch: [string, number];
};
export interface StringSimilarity {
compare: StringSimilarityCompareFunc;
bestMatch: (value: string, targets: string[]) => BestMatchResult;
}

export function findBestMatch(mainString: string, targetStrings: string[]) {
if (!areArgsValid(mainString, targetStrings)) throw new Error('Bad arguments: First argument should be a string, second should be an array of strings');

const ratings = [];
let bestMatchIndex = 0;

for (let i = 0; i < targetStrings.length; i++) {
const currentTargetString = targetStrings[i];
const currentRating = compareTwoStrings(mainString, currentTargetString);
ratings.push({ target: currentTargetString, rating: currentRating });
if (currentRating > ratings[bestMatchIndex].rating) {
bestMatchIndex = i;
}
}

const bestMatch = ratings[bestMatchIndex];

return { ratings: ratings, bestMatch: bestMatch, bestMatchIndex: bestMatchIndex };
export enum StringSimilarityAlgs {
DiceCoefficient = 'dice_coefficient',
}

function areArgsValid(mainString: string, targetStrings: string[]) {
if (typeof mainString !== 'string') return false;
if (!Array.isArray(targetStrings)) return false;
if (!targetStrings.length) return false;
if (
targetStrings.find(function (s) {
return typeof s !== 'string';
})
)
return false;
return true;
}
export const stringSimilarityAlgs: Record<StringSimilarityAlgs, StringSimilarity> = {
dice_coefficient: {
compare: diceCoefficient,
bestMatch(value, targets) {
const ratings: [string, number][] = [];
let bestMatchIndex = 0;
for (let i = 0; i < targets.length; i++) {
const currentTargetString = targets[i];
const currentRating = diceCoefficient(value, currentTargetString);
ratings.push([currentTargetString, currentRating]);
if (currentRating > ratings[bestMatchIndex][1]) {
bestMatchIndex = i;
}
}
const bestMatch = ratings[bestMatchIndex];
return { ratings: ratings, bestMatch: bestMatch };
},
},
};
26 changes: 26 additions & 0 deletions src/utils/stringSimilarity/dice.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { StringSimilarityCompareFunc } from '../stringCorrection';

const dicesCoefficient: StringSimilarityCompareFunc = (first, second) => {
first = first.replace(/\s+/g, '');
second = second.replace(/\s+/g, '');
if (first === second) return 1; // identical or empty
if (first.length < 2 || second.length < 2) return 0; // if either is a 0-letter or 1-letter string
const firstBigrams = new Map();
for (let i = 0; i < first.length - 1; i++) {
const bigram = first.substring(i, i + 2);
const count = firstBigrams.has(bigram) ? firstBigrams.get(bigram) + 1 : 1;
firstBigrams.set(bigram, count);
}
let intersectionSize = 0;
for (let i = 0; i < second.length - 1; i++) {
const bigram = second.substring(i, i + 2);
const count = firstBigrams.has(bigram) ? firstBigrams.get(bigram) : 0;
if (count > 0) {
firstBigrams.set(bigram, count - 1);
intersectionSize++;
}
}
return (2.0 * intersectionSize) / (first.length + second.length - 2);
};

export default dicesCoefficient;

0 comments on commit 947425f

Please sign in to comment.