From a7216352dc2eb7c472c8eac965bb5b55bd6c185e Mon Sep 17 00:00:00 2001 From: Niklas Marion Date: Wed, 19 Jun 2024 11:30:24 +0200 Subject: [PATCH] calcualte levenshtein distance for search results --- src/athletes/athlete.dto.ts | 5 +++++ src/athletes/athletes.service.ts | 12 ++++++++++-- src/levenshtein-distance.ts | 19 +++++++++++++++++++ 3 files changed, 34 insertions(+), 2 deletions(-) create mode 100644 src/levenshtein-distance.ts diff --git a/src/athletes/athlete.dto.ts b/src/athletes/athlete.dto.ts index 1127456..592fbcc 100644 --- a/src/athletes/athlete.dto.ts +++ b/src/athletes/athlete.dto.ts @@ -121,4 +121,9 @@ export class AthleteSearchResult { sex: 'MALE' | 'FEMALE' | null; @ApiProperty({ nullable: true, type: Date }) birthdate: Date | null; + @ApiProperty({ + type: Number, + description: 'Levenshtein distance between search query and athlete name', + }) + levenshteinDistance: number; } diff --git a/src/athletes/athletes.service.ts b/src/athletes/athletes.service.ts index 6d3e757..96f6a76 100644 --- a/src/athletes/athletes.service.ts +++ b/src/athletes/athletes.service.ts @@ -9,6 +9,7 @@ import parseVenue from './venue.utils'; import mapDisciplineToCode from 'src/discipline.utils'; import { formatLastname } from 'src/name.utils'; import { GraphqlService } from 'src/graphql/graphql.service'; +import { levenshteinDistance } from 'src/levenshtein-distance'; @Injectable() export class AthletesService { @@ -27,7 +28,7 @@ export class AthletesService { searchCompetitors: z.array(AthleteSearchSchema), }) .parse(data); - return response.searchCompetitors.map((item) => { + const searchResults = response.searchCompetitors.map((item) => { const sex = item.gender === 'Men' ? 'MALE' @@ -40,9 +41,16 @@ export class AthletesService { firstname: item.givenName, lastname: formatLastname(item.familyName), birthdate: item.birthDate, + levenshteinDistance: levenshteinDistance( + name.toLowerCase().trim(), + (item.givenName + ' ' + item.familyName).toLowerCase().trim(), + ), sex, }; - }); + }) as AthleteSearchResult[]; + return searchResults.sort( + (a, b) => a.levenshteinDistance - b.levenshteinDistance, + ); } catch (error) { console.error(error); Sentry.captureException(error); diff --git a/src/levenshtein-distance.ts b/src/levenshtein-distance.ts new file mode 100644 index 0000000..d0c27cc --- /dev/null +++ b/src/levenshtein-distance.ts @@ -0,0 +1,19 @@ +export const levenshteinDistance = (s, t) => { + if (!s.length) return t.length; + if (!t.length) return s.length; + const arr = []; + for (let i = 0; i <= t.length; i++) { + arr[i] = [i]; + for (let j = 1; j <= s.length; j++) { + arr[i][j] = + i === 0 + ? j + : Math.min( + arr[i - 1][j] + 1, + arr[i][j - 1] + 1, + arr[i - 1][j - 1] + (s[j - 1] === t[i - 1] ? 0 : 1), + ); + } + } + return arr[t.length][s.length]; +};