modules searchWithError.js
/*
Принцип: если было более 30% совпадений сочетаний 3 букв, то будет список отсортированный по количеству совпадений
Если все сочетания 3х букв слова, которое ищем, нашлись в названии, которое 3 раза перевёрнуто по клавиатуре Привет Ghbdtn Privet Зкшмуе.
Значит это то, что искали..
Иначе выставляем сколько % найденно.
И потом сортируем по этим %
*/
export const searchWithError = {
'versionDate':'2023-08-10',
'minProbability' : 30,
'delay' : 300,
'quantityСharacters' : [2,3],
'data' : {
'list':[]
},
/*
Получение данных в виде
ключ : текст для поиска
так оно в более привычном и минимальном виде
searchWithError.setDatabase({key:value, key:value})
*/
'setDatabase' : (data) => {
searchWithError.data.list = Object.keys(data).map(key => ({
'key': key,
'value': data[key],
'numbers': searchWithError.textToFullSetNumbers(data[key])
}))
},
/*
Преобразовать одно слово в соединении букв в виде цифр
Привет
[[п,р,и],[р,и,в],[и,в,е],[в,е,т]]
[[1087,1088,1080],[1088,1080,1074],[1080,1074,1077],[1074,1077,1090]]
[108710881080, 108810801074, 108010741077, 107410771090]
Тоже самое и с 2 буквами и объединяет эти массивы
*/
'textToSetNumbers' : (text) => {
if (text === '')
return new Set();
let characters = text.toLowerCase().split('')
//new Set(
return new Set(
searchWithError.quantityСharacters.map(quantity =>
Array.from({
'length' : (text.length + 1 - quantity)
}, (_, i) => Number(characters.slice(i, i + quantity)
.map(character => character.charCodeAt())
.join('')
)
)
)
.flat()
)
},
/*
Вывести все варианты слова в виде массива
Привет
[привет, ghbdtn, privet, зкшмук]
*/
'textToVariantsText' : (text) => {
text = text.toLowerCase()
return Array.from(new Set([
text,
searchWithError.textRuToEn(text), // бруско => brusko
searchWithError.textEnToRu(text),
searchWithError.textRuKeyToEn(text), // икгылщ => brusko
searchWithError.textEnKeyToRu(text), // hecrj => бруско
searchWithError.textRuKeyToEn(searchWithError.textEnKeyToRu(text)), // hecrj => бруско => brusko
searchWithError.textRuKeyToEn(searchWithError.textEnToRu(text))
//searchWithError.textEnToRu(searchWithError.textRuKeyToEn(text))
]))
},
/*
Преобразовать слово варианты слов,
варианты в цифры соединений букв
и всё в один сет
Привет
[привет, ghbdtn, privet, зкшмук]
Set(2) { 1, 2 } Set(2) { 1, 2 } Set(2) { 1, 2 }
Set(7) { 1, 2, 3, 4, 5, 6, 7 }
*/
'textToFullSetNumbers': (text) => {
let variantsText = searchWithError.textToVariantsText(text)
return variantsText
.map(text => searchWithError.textToSetNumbers(text))
.reduce((result, set0) => {
set0.forEach((value) => result.add(value))
return result;
}, new Set())
},
'lettersMapping': {
'RuToEn' : {
'а': 'a', 'б': 'b', 'в': 'v',
'г': 'g', 'д': 'd', 'е': 'e',
'ё': 'ye', 'ж': 'zh', 'з': 'z',
'и': 'i', 'й': 'y', 'к': 'k',
'л': 'l', 'м': 'm', 'н': 'n',
'о': 'o', 'п': 'p', 'р': 'r',
'с': 's', 'т': 't', 'у': 'u',
'ф': 'f', 'х': 'h', 'ц': 'c',
'ч': 'ch', 'ш': 'sh', 'щ': 'shch',
'ъ': '', 'ы': 'y', 'ь': '', 'э': 'e',
'ю': 'yu', 'я': 'ya', ' ':' '
},
'EnToRu': {
'a':'а', 'b':'б', 'c':'ц',
'd':'д', 'e':'е', 'f':'ф',
'g':'г', 'h':'х', 'i':'и',
'j':'ж', 'k':'к', 'l':'л',
'm':'м', 'n':'н', 'o':'о',
'p':'п', 'q':'к', 'r':'р',
's':'с', 't':'т', 'u':'у',
'v':'в', 'w':'в', 'x':'кс',
'y':'й', 'z':'з'
},
'EnKeyToRu' : {
'q':'й', 'w':'ц', 'e':'у',
'r':'к', 't':'е', 'y':'н',
'u':'г', 'i':'ш', 'o':'щ',
'p':'з', '[':'х', ']':'ъ',
'a':'ф', 's':'ы', 'd':'в',
'f':'а', 'g':'п', 'h':'р',
'j':'о', 'k':'л', 'l':'д',
';':'ж', '\'':'э', 'z':'я',
'x':'ч', 'c':'с', 'v':'м',
'b':'и', 'n':'т', 'm':'ь',
',':'б', '.':'ю', ' ':' '
},
'RuKeyToEn': {
' ':' ', 'а':'f', 'б':',',
'в':'d', 'г':'u', 'д':'l',
'е':'t', 'ж':';', 'з':'p',
'и':'b', 'й':'q', 'к':'r',
'л':'k', 'м':'v', 'н':'y',
'о':'j', 'п':'g', 'р':'h',
'с':'c', 'т':'n', 'у':'e',
'ф':'a', 'х':'[', 'ц':'w',
'ч':'x', 'ш':'i', 'щ':'o',
'ъ':']', 'ы':'s', 'ь':'m',
'э':'\'', 'ю':'.', 'я':'z'
},
'textTo': (text, lettersMapping) => {
return text
.toLowerCase()
.split('')
//.filter(character => lettersMapping[character])
.map(character => lettersMapping[character] ? lettersMapping[character] : character)
.join('')
}
},
// бруско => brusko
'textRuToEn': (text) => {
return searchWithError.lettersMapping.textTo(text, searchWithError.lettersMapping.RuToEn)
},
'textEnToRu': (text) => {
return searchWithError.lettersMapping.textTo(text, searchWithError.lettersMapping.EnToRu)
},
// икгылщ => brusko
'textRuKeyToEn' : (text) => {
return searchWithError.lettersMapping.textTo(text, searchWithError.lettersMapping.RuKeyToEn)
},
// ,hecrj => бруско
'textEnKeyToRu' : (text) => {
return searchWithError.lettersMapping.textTo(text, searchWithError.lettersMapping.EnKeyToRu)
},
/*
Основная функция поиска
searchWithError.search('Привет', console.log)
[key, key, key]
searchWithError.search('Привет', console.log, {'full':true})
[[key: item], [key: item]]
*/
'search' : (searchText = '', callback = console.log, options = {}) => {
if (searchWithError.data.list.length === 0) {
alert(`Нет базы для поиска,
воспользуйтесь функцией searchWithError.setDatabase({key:value, key:value}),
а после searchWithError.search()`)
return;
}
// Таймер, чтоб не реагировал до тех пор, пока полностью не введут слово
clearTimeout(searchWithError.timer)
searchWithError.timer = setTimeout(() => {
// При очистке поиска очищать результат
if(searchText === '' || searchText.length < 3) {
searchWithError.handleResponse(callback, [], options.arg)
return;
}
// Текст для поиска переводим в массив
let searchNumbers = searchWithError.textToSetNumbers(searchText)
let searchNumbersArray = Array.from(searchNumbers)
// Подсчёт процента, фильтр, сортировка
let result = searchWithError.data.list
.map(item => {
// Количество доступных совпадений
item.quantityСoincidencePossible = searchNumbersArray.length
// Количество найденных совпадений
item.quantityCoincidenceFound = searchNumbersArray.filter(number => item.numbers.has(number)).length
// Процент найденных совпадений // Math.floor(
item.percentCoincidence = item.quantityCoincidenceFound * 100 / item.quantityСoincidencePossible
return item
})
// Оставляем с нормальным процентом
.filter(item => item.percentCoincidence >= searchWithError.minProbability)
// Сортируем по количеству найденных совпадений
.sort((item0, item1) => item0.quantityCoincidenceFound == item1.quantityCoincidenceFound ?
0 : (item0.quantityCoincidenceFound < item1.quantityCoincidenceFound ? 1 : -1)
)
// Если задано количество, то оставляем нужное
if (options.quantity)
result = result
.filter((_, index) => index < options.quantity)
// Полный вывод в виде масивов [[key: item], [key: item]]
if (options.full)
result = result.map(item => {
item.variants = searchWithError.textToVariantsText(item.value)
return [item.key, item]
})
else
// Обычный вывод в виде масивов ключей [key, key, key]
result = result.map(item => item.key)
searchWithError.handleResponse(callback, result, options.arg)
}, searchWithError.delay)
},
// Обработка ответа
'handleResponse' : (callback = console.log, data, arg) => {
if (data === undefined)
callback()
else if (arg === undefined)
callback(data)
else
callback(data, arg)
}
}