Skip to content

Commit 1c1f1eb

Browse files
committed
feat: added typo tolerance through fuzzy search
1 parent f128677 commit 1c1f1eb

2 files changed

Lines changed: 87 additions & 2 deletions

File tree

src/components/CommunityPortal/Activities/ActivityList.jsx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { useState, useEffect } from 'react';
33
import { useSelector } from 'react-redux';
44
import styles from './ActivityList.module.css';
55
// import { useHistory } from 'react-router-dom';
6+
import { fuzzySearch } from '../../../utils/fuzzySearch';
67

78
function ActivityList() {
89
const [activities, setActivities] = useState([]);
@@ -132,9 +133,9 @@ function ActivityList() {
132133

133134
const filteredActivities = activities.filter(activity => {
134135
return (
135-
(!filter.type || activity.type.toLowerCase().includes(filter.type.toLowerCase())) &&
136+
(!filter.type || fuzzySearch(activity.type, filter.type, 0.5)) &&
136137
(!filter.date || activity.date === filter.date) &&
137-
(!filter.location || activity.location.toLowerCase().includes(filter.location.toLowerCase()))
138+
(!filter.location || fuzzySearch(activity.location, filter.location, 0.5))
138139
);
139140
});
140141

src/utils/fuzzySearch.js

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
export const levenshteinDistance = (str1, str2) => {
2+
const s1 = str1.toLowerCase();
3+
const s2 = str2.toLowerCase();
4+
5+
const track = Array(s2.length + 1).fill(null).map(() => Array(s1.length + 1).fill(0));
6+
7+
for (let i = 0; i <= s1.length; i++){
8+
track[0][i] = i;
9+
}
10+
11+
for (let j = 0; j <=s2.length; j++){
12+
track[j][0] = j;
13+
}
14+
15+
for (let j = 1; j <= s2.length; j++){
16+
for (let i = 1; i <= s1.length; i++){
17+
const indicator = s1[i-1] === s2[j-1] ?0 : 1;
18+
track[j][i] = Math.min(
19+
track[j][i-1] + 1,
20+
track[j-1][i] + 1,
21+
track[j - 1][i - 1] + indicator,
22+
)
23+
}
24+
}
25+
return track[s2.length][s1.length];
26+
};
27+
28+
export const calculateSimilarity = (str1, str2) => {
29+
30+
if(!str1 || !str2){
31+
return 0;
32+
}
33+
34+
const distance = levenshteinDistance(str1, str2);
35+
const maxLength = Math.max(str1.length, str2.length);
36+
37+
return 1 - distance / maxLength;
38+
};
39+
40+
export const fuzzySearch = (input, searchText, threshold = 0.6) => {
41+
42+
if(!searchText || !searchText.trim()){
43+
return true;
44+
}
45+
46+
if(!input){
47+
return false;
48+
}
49+
50+
const trimmedSearch = searchText.trim().toLowerCase();
51+
const inputLower = input.toLowerCase();
52+
53+
if(inputLower.includes(trimmedSearch)){
54+
return true;
55+
}
56+
57+
if(trimmedSearch.length < 2){
58+
return false;
59+
}
60+
61+
const similarity = calculateSimilarity(input, searchText);
62+
63+
if(similarity >= threshold){
64+
return true;
65+
}
66+
67+
const searchWords = trimmedSearch.split(/\s+/);
68+
const inputWords = inputLower.split(/\s+/);
69+
70+
return searchWords.every(searchWord => {
71+
if(searchWord.length < 2){
72+
return true;
73+
}
74+
return inputWords.some(inputWord => {
75+
76+
if (inputWord.startsWith(searchWord)) {
77+
return true;
78+
}
79+
80+
const wordSimilarity = calculateSimilarity(inputWord, searchWord);
81+
return wordSimilarity >= threshold;
82+
});
83+
});
84+
};

0 commit comments

Comments
 (0)