Группируйте аналогичные строки из массива в Node.js
Скажем, у меня есть набор различных URL-адресов в массиве:
var source = ['www.xyz.com/Product/1', 'www.xyz.com/Product/3', 'www.xyz.com/Category/1', 'somestring']
Что было бы хорошим способом перебора массива и группировки подобных строк в отдельный массив?
Желаемый результат из приведенного выше примера:
var output = [
['www.xyz.com/Product/1', 'www.xyz.com/Product/3'],
['www.xyz.com/Category/1'],
['somestring']
];
Условия
- Все элементы в
source
могут быть случайными строками
- Логика должна иметь возможность сравнивать и группировать около 100 000 элементов в значимое время.
Я нашел библиотеку схожести строк, которая дает возможность сравнить одну строку с коллекцией строк. Один из способов - перебрать исходный код, сравнить каждый элемент с исходной коллекцией и применить правило для группировки элементов с аналогичным счетом. Однако, я думаю, это было бы ужасно неэффективно.
Может ли кто-нибудь предложить мне эффективный способ выполнить то, что мне нужно?
Ответы
Ответ 1
Лучшее решение, о котором я могу думать, - сравнить строки друг с другом и проверить, насколько они отличаются друг от друга. Существует алгоритм, который выполняет алгоритм Levenshtein distance:
Расстояние Левенштейна является строковой метрикой для измерения разница между двумя последовательностями. Неформально расстояние Левенштейн между двумя словами - минимальное количество односимвольных изменений (то есть вставки, удаления или замены), необходимые для изменения одного слово в другое.
Мы можем легко создать фильтр Левенштейна поверх модуль fast-levenshtein:
const levenshtein = require('fast-levenshtein');
const levenshteinFilter = (source, maximum = 5) => {
let _source, matches, x, y;
_source = source.slice();
matches = [];
for (x = _source.length - 1; x >= 0; x--) {
let output = _source.splice(x, 1);
for (y = _source.length - 1; y >= 0; y--) {
if (levenshtein.get(output[0], _source[y]) <= maximum) {
output.push(_source[y]);
_source.splice(y, 1);
x--;
}
}
matches.push(output);
}
return matches;
}
let source = ['www.xyz.com/Product/1', 'www.xyz.com/Product/3', 'www.xyz.com/Category/1', 'somestring'];
let output = levenshteinFilter(source);
// [ [ 'www.xyz.com/Product/1', 'www.xyz.com/Product/3' ],
// [ 'www.xyz.com/Category/1' ],
// [ 'somestring' ] ]
Вы можете определить максимально допустимое расстояние в 2 аргументах функции (по умолчанию - 5).
Ответ 2
Как насчет MinHash (https://en.wikipedia.org/wiki/MinHash)?
Он был разработан для поиска повторяющихся веб-страниц. Поэтому я предполагаю, что вы можете url.split('/') и обрабатывать каждый URL как набор слов.
Ответ 3
Вы не воплощаете свои намерения, но если перед вами стоит задача найти выбранные предметы ближайших соседей из случайного стога сена, я бы, вероятно, попытался построить дерево хешей.
Или, и это может быть обманом, я позволю библиотеке сделать это для меня. lunr.js - это в основном чистый индекс JS lucene, я бы подталкивал ваш массив к нему и запрашивал его, чтобы получить аналогичные строки. Раньше у меня были довольно большие наборы данных в lunr.js, и он очень эффективен, и нет свечи, когда поблизости есть кластер elasticsearch, но все же впечатляющий.
Если вы предоставите более подробную информацию о том, что вы пытаетесь сделать, я мог бы дать более подробную информацию и, возможно, даже пример кода.
Ответ 4
Если источник содержит все случайные URL-адреса, функция ниже даст ожидаемый результат, как показано в вопросе.
function filter (source) {
var output = []
source.forEach((svalue) => {
if (output.length === 0) {
output.push([svalue])
} else {
var done = false
output.forEach((tarr) => {
if (!done) {
tarr.forEach((tvalue) => {
if (svalue.indexOf('/') > -1 && svalue.split('/').slice(0, 2).join('/') == tvalue.split('/').slice(0, 2).join('/')) {
tarr.push(svalue)
done = true
}
})
}
})
if (!done) {
output.push([svalue])
done = true
}
}
})
return output
}
Ответ 5
Основываясь на ваших примерах тестов, я могу предложить вам реализовать дерево Radix или дерево префикса для хранения строк. После этого вы можете определить критерии для группировки этих строк.