Потому что иногда бюрократия мешает.

Пару лет назад меня попросили создать репозиторий файлов для закрытого репозитория conda. Идея заключалась в том, что из соображений безопасности у нас будет список разрешенных библиотек внутри очень дорогой защищенной среды и только эти библиотеки. Это была достаточно простая установка, и для ее доставки требовался только большой объем памяти и простой статический веб-сервер.
Простое зеркало с фильтрами позволило нам гораздо быстрее реагировать на потребности пользователей и очень быстро добавлять или удалять библиотеки из защищенной среды. Это означало, что команда безопасности гораздо охотнее допускала пакеты в среду, зная, что на их удаление уйдут часы, а не месяцы или годы.
- Они были так взволнованы, что мы начали использовать шаблоны для пакетов.
- Мы быстро перешли от десятков посылок к тысячам.
- Пользователи разработали новый вопрос, чтобы позвонить и задать:
какие библиотеки доступны?
Список доступных библиотек был меньше, чем в официальных репозиториях, но значительно больше, чем можно было бы разумно прочитать человеку, он также был разбросан по нескольким папкам. В общем, уследить было сложно.
Пользователям требовалась поисковая система, такая как Anaconda.org, но ограниченная только пакетами, доступными в нашей организации.
В стороне
Именно в этот момент в нашей истории я оказался вдали от дома по делам. Посетив вечернюю лекцию в пабе Гатино, Квебек, посвященную использованию технических систем для воздействия на социальные изменения, и выпив несколько напитков, я обнаружил, что возвращаюсь в свой отель в одиночестве посреди канадской зимы. Пытаясь не замерзнуть и выяснить, где мой отель, я начал думать о проблемах с поиском. К тому времени, как я вернулся в свой отель, я уже не мог спать, поэтому начал реализовывать решение.
Аааа… жизнь программиста.
К сожалению, бюрократия встала на пути. Получение статического HTTP-сервера было подвигом переговоров. Пытаясь обеспечить общий доступ к файловой системе через HTTP, я прямо заявил, что при этом будет включен только статический рендеринг. Это позволило нам пропустить месяцы оценок безопасности, отсутствие обработки на стороне сервера означало отсутствие риска для безопасности сети.
Чтобы удовлетворить как потребность в поисковой системе, так и отсутствие обработки на стороне сервера, я создал простую поисковую систему внутри браузера.
Выполнение
Я действительно делал это пару раз раньше. В моей карьере было несколько случаев, когда получение инструментов и компьютеров было серьезным препятствием для простого механизма обработки данных. Ключом к этому является то, что вы можете поместить простой файл html в обслуживаемое вами местоположение, а затем он может искать данные, хранящиеся на сервере.
Для удобства читателей у меня есть простой рабочий пример, который индексирует Всемирную книгу фактов ЦРУ, а затем предлагает поиск по содержимому. Результаты поиска предлагают ссылку на реальную страницу на веб-сайте ЦРУ.
Автоиндекс и загрузка
Ключом к этому является обеспечение того, чтобы какая-либо форма автоиндексирования была включена. Сервер должен объявить доступные наборы данных для обработки, чтобы клиент обнаружил, что им нужна обработка. Есть несколько способов добиться этого, самый простой — включить службу на сервере.
В подобных системах я также использовал сценарии `bash` или `PowerShell`, чтобы просто перечислить все имеющиеся наборы данных: никакой информации о них, только то, что они присутствуют.
ls --format=single-column ./data/* > index.txt
Затем местоположение этого файла может быть передано сценарию в качестве отправной точки для сбора всей информации.
const basePath = window.location;
const datasets = `${basePath}/index.txt`;
Когда у нас есть список данных для агрегирования, мы можем начать процесс загрузки, анализа и хранения.
Учитывая, что мы использовали PouchDB, мы можем сразу начать загрузку текста в локальную базу данных.
const db = new PouchDB('searcher');
async function LoadDB(){
let recs = await fetch('./index.txt');
recs = await rec.text();
recs = rec.split('\n');
for(let loc of recs){
let content = await fetch(loc);
let nRec = {
_id: loc,
text: nRec
};
let oRec = await db.get(loc);
if(RecordsDiffer(nRec,oRec)){
db.put(nRec);
}
}
}
Полный текст теперь хранится в базе данных для дальнейшего использования пользователем. Рекомендуется периодически сканировать его, чтобы увидеть, не произошли ли какие-либо изменения. (загрузчик.js)
Очистка текста
При разработке баз данных и поиске индекс можно рассматривать как краткий справочник. Во времена ручного поиска карточный каталог предлагал поиск по алфавиту, упорядоченный по теме, что позволяло быстро искать. Вместо того, чтобы просматривать каждую книгу во всем здании, вы можете просмотреть меньший список в одной коробке.
Меньше значит больше, меньше значит быстрее.
Для целей этого обсуждения полезно иметь несколько документов, которые мы хотим найти:
Факт 21. На каждые 25% увеличения сложности задачи приходится 100% увеличение сложности программного решения. это не условие, чтобы пытаться что-то изменить (хотя снижение сложности всегда желательно); так оно и есть.
Факт 22: Восемьдесят процентов работы над программным обеспечением интеллектуальны. Изрядное количество этого является творческим. Немного канцелярского.
- Роберт Л. Гласс, Факты и заблуждения программной инженерии.
Таким образом, наш образец для обсуждения будет вращаться вокруг индексации текста Glass's Facts, чтобы обеспечить быстрый поиск. Это может быть полезно прикрепить к микрофону в офисе, который затем отображает соответствующий факт в зависимости от о чем идет речь.
Каждый факт может быть помещен в отдельную запись для последующего индексирования. Это позволит нам рассматривать каждый факт как отдельную сущность.
./data/fact-01.txt ./data/fact-02.txt ./data/fact-03.txt
Одна из проблем с таким текстом заключается в том, что большая часть текста на самом деле бессмысленна или, по крайней мере, несет мало смысла. Некоторый уровень преобразования должен быть выполнен для удаления ненужной информации.
function sanitize(text){
text = text.toLowerCase();
text = text.replace(/[^a-z]/g,'.');
text = text.split('.');
text = text.filter(d=>{return d.length > 3;});
return text;
}
let sanitized = sanitize(facts[21]);
В рамках индексации факта номер 21 мы удаляем
- случай
- не текст
- короткие слова (три символа, они же: Стоп-слова)
в результате получается очищенный список слов. (crepo.js)
[ 'every', 'percent', 'increase', 'problem', 'complexity', 'there', 'percent', 'increase', 'complexity', 'software', 'solution', 'that', 'condition', 'change', 'even', 'though', 'reducing', 'complexity', 'always', 'desirable', 'thing', 'that', 'just' ]
Список можно еще больше сократить, отметив повторяющиеся слова. Подсчет слов — полезный способ взвесить значение термина в поиске.
function WordCount(list){
let count = {};
for(let word of list){
count[word] = count[word] || 0;
count[word]++;
}
return count;
}
let count = WordCount(sanitized);
Создание индекса
Для целей этого поиска я решил найти соответствие каждому слову. Это было сделано для простоты и потому, что это позволяет искать слова не по порядку.
Примеры типов поиска, которые я хочу, чтобы пользователь мог использовать:
- `сложность`: слишком сложное слово (может быть
complex) - `ompl`: слово, которое является частичным совпадением
- `программное обеспечение`: несколько слов
- `программное решение`: слова не по порядку
Эти примеры представляют несколько разных случаев, которые нам нужно обработать. Наиболее интересен сценарий «частичное совпадение».
Чтобы создать индекс, нам нужен список, основанный на нашем поисковом значении, которое
| key | score | document | | -------- | ----- | -------- | | database | 100 | <url> | | atabase | 99 | <url> | | tabase | 98 | <url> | | abase | 97 | <url> | | base | 96 | <url> | | ase | 95 | <url> | | databas | 99 | ... | | databa | 98 | ... | | ... | ... | ... |
Когда вы ищете «поиск данных», он находит
document 1 data 97 document 1 search 100
Всего 197 баллов, а затем сортировка по документам с наивысшей оценкой (Код: search.js)
Дополнительно
лемматизация
В качестве еще одного слоя можно рассмотреть лемму. Лемматизация относится к самому основному слову, которое представлено словом. Например, intellectual можно рассматривать как то же самое, что и intellect. Это поможет уменьшить количество возможных типографских различий, которые возникнут позже, например, человек, который помнит that is, должен найти that's, если подумать, is — это короткое слово.
https://github.com/michmech/lemmatization-lists/blob/master/lemmatization-en.txt
every 25 percent increase problem complex there 100 percent increase complex software solution that condition change even though reduce complex always desire thing that just
Это гораздо более простая версия текста, которая уменьшит количество информационной энтропии (то есть вещей, которые могут пойти не так в моей голове при запоминании).
Этот текст хорошо продезинфицирован и готов к индексации.
Заключение
На следующее утро, после написания решения, я был очень доволен собой за то, что у меня есть готовое решение, которое я могу отправить по электронной почте своим основным клиентам (нескольким менеджерам по науке о данных в организации). Я был так доволен, что показал его коллеге, который помог мне с оптимизацией индекса.

Спустя годы организация по-прежнему отслеживает доступные пакеты conda.
Справедливости ради, единственная причина, по которой я смог сделать это за одну ночь, заключалась в том, что я реализовал это так много раз. Я использовал индексы на стороне браузера для реализации информационных панелей для регрессионного тестирования, распределения работы сотрудников, поиска в личных блогах и поисковой системы Project Gutenberg. Это даже стало основой для курса, который я вел по вводному JavaScript.
Это одна из причин, почему я люблю JavaScript: он всегда доступен для обработки данных, какая бы безумная идея ни пришла вам в голову в этот момент.
С момента написания этой статьи я обнаружил, что Лоусон на самом деле написал полнотекстовый поиск для PouchDB. Вам следует использовать его решение на базе lunr engine.
Неоптимальный
Это не оптимальное решение.
В этом случае каждый пользователь поисковой системы должен загрузить и сгенерировать собственный экземпляр индекса. Это требует, чтобы клиент загружал весь набор данных в первый раз (увеличивая сетевой трафик) для каждого используемого браузера.
В панели регрессионного тестирования первоначальная загрузка и анализ результатов теста заняли примерно 3 часа. Кэширование означало, что это было почти мгновенно, если вы обновлялись каждый день, но первая загрузка была большой.
Одним из ключевых преимуществ обработки на стороне сервера является возможность принимать входящие данные и генерировать индексы один раз для всех клиентов. Вы можете разделить разницу, создав индекс на сервере и загрузив созданный индекс клиентами. Это разделит нагрузку по обработке между центральным процессором для центральных данных и распределенной обработкой для отдельных поисков.
Это намекает на интересный баланс между общей и распределенной обработкой. Используя базы данных, поддерживающие протокол обмена CouchDB (CouchBase, CouchDB, PouchDB, CloudAnt), каждый пользователь может обрабатывать новые данные по мере их нахождения, а также делиться результатами с центральный бассейн, которым пользуется следующий человек. Эта форма обработки данных с отложенной загрузкой имеет некоторые теоретические преимущества (в доверенной среде), поскольку она требует очень небольшой центральной обработки (дорогой) и вместо этого использует рабочие станции, которые уже используются. В то же время он разделил бы рабочую нагрузку, чтобы гарантировать, что ни один компьютер не возьмет на себя основную тяжесть всех вычислений.
Наконец, если это решение вас заинтриговало, вам следует изучить поисковые запросы на основе Lucene, предоставляемые для CouchDB и включенные во все коммерческие предложения.
Пожалуйста, оставьте комментарий или задайте вопрос, и если вы нашли этот контент ценным, не забудьте нажать Подписаться.
Дополнительные материалы на PlainEnglish.io. Подпишитесь на нашу бесплатную еженедельную рассылку новостей. Подпишитесь на нас в Twitter и LinkedIn. Присоединяйтесь к нашему сообществу Discord.