Салимóненко Дмитрий Александрович
Разное
Одно время я делал поиск по одному слову, поиск по регулярному выражению. А также поиск по ключевым словам. А потом заинтересовался нечетким поиском. Ну, т.е. чтобы можно было находить слова, близкие по звучанию и не учитывать, скажем, разные падежи, окончания. По идее, подобный поиск, конечно, уже давно осуществляют поисковые системы, типа Яндекса или Google. Не говоря уж об искусственном интеллекте (ИИ).
Это все здорово, но как искать информацию на сервере, т.е. у себя? Там, куда Яндекс может просто не попасть. Можно, конечно, установить ИИ у себя. Но, так думаю, что это будет по подписке, т.е. за оплату. А мне это невыгодно, т.к. подобные задачи возникают нечасто, хотя и систематически. Дело в том, что с годами накапливается все больше и больше интересной информации. И иногда уже забывается кое-что. Забывается иногда даже сам факт наличия информации о каком-то вопросе или предмете.
Ранее я выходил из положения при помощи своей простенькой справочной системы. Это - вебстраница, открываемая в браузере, сделанная по аналогии с Проводником Windows. Т.е. слева - ссылки (в том числе, сгруппированные в единые блоки), а справа - содержимое какой-нибудь ссылки, по которой был сделан клик. Да, это удобно, но когда объем документов там перевалил за многие сотни... я стал там слегка теряться.
Простой поиск слову (или его части), а также по регулярному выражению какое-то время выручал. И выручает до сих пор. Однако, со временем длительность такого поиска стала увеличиваться. Потом, иногда встречаются слова, близкие по синтаксису (и звучанию). И не всегда при поиске возможно указать точный синтаксис.
В общем, решил я сделать собственную поисковую систему
Есть, конечно, уже готовые решения. Например, в том же PHPStorm после нажатия Ctrl + Shft + F можно сделать (довольно быстрый) поиск по любым символам, фразам, присутствующим в файлах на (локальном, конечно же) сайте. Однако, во-первых, PHPStorm - достаточно тяжелая программа, он медленно загружается. Потом, вроде бы, он не позволяет осуществлять именно нечеткий поиск.
Есть также разного рода плагины для сайтов. Я с ними не работал, но люди пишут, что это все работоспособно. Видимо, примерно так и реализуется подобный поиск на сайтах. Когда начинаешь вводить некую фразу, а сервер в ответ выдает предполагаемые варианты - из состава тех, которые имеются у него. Честно сказать, НЕОХОТА мне связываться с чужими решениями.
Почему? Да, потому, что в итоге будут ограничения и неудобства. Например, выявится какая-нибудь уязвимость (так думаю, с вероятностью 80-90%). Потребуется обновление. А это - обновление и самого РНР и, быть может, даже обновление браузера. А ни то, ни другое мне вообще неинтересно. Ибо мне это нужно для РАБОТЫ (и т.п.), а не для того, чтобы обновлять... для того, чтобы потом обновлять (а потом - снова обновлять).
Кроме того, возможно, там не будет нужной мне функциональности. Кроме того, подобные решения, требуют, как правило, установки еще чего-то там. Т.е. это - такое себе. В итоге, конечно, если настроить все правило, будет работать. Но... не хочется потом связываться с необходимостью обновлений. Это занимает немало времени и усилий.
Как сделал
Честно говоря, описать довольно сложно, ибо логика там непростая. Любопытно, что даже сам слегка путаюсь, хотя везде, где надо и не надо, наделал хорошие комментарии (ну, как это у меня принято). В общем, попробую описать по этапам, как оно работает.
1. Вначале следует получить список всех файлов сайта. Каждому файлу присваивается свой уникальный числовой индекс, начиная с 1.
2. Затем нужно проиндексировать весь сайт. Все файлы, индексация которых разрешена (это, например, файлы с расширениями txt, html) и которые получили уникальный числовой индекс, просматриваются программой-индексатором, читается их содержимое.
3. Я взял необязательные русские слова, поиск которых точно не будет осуществляться. Это - предлоги, местоимения, союзы, частицы и междометия. Оказалось... наш русский язык-то достаточно конечен. И не столь уж много там таких частей речи. У меня получился список несколько превышающий 1200 слов. Возможно, я взял не всё. Так вот, эти слова удаляются из содержимого каждого индексируемого файла.
4. Затем содержимое индексируемого файла переводится в транслит. Это - один из этапов достижения нечеткого поиска.
5. Затем делается преобразование транслитированного слова при помощи функции metaphone языка PHP. Это очень удобная функция, она преобразует англоязычные слова и их укороченные "слоганы". Причем, такие, что они звучат очень сходно с исходным словом. Например, слово "время" после транслита и применения этой функции преобразуется в frmy. Точно также будет преобразовано слово "фремя" (хотя, такого слова в русском языке пока нет). Слово "каталог" преобразуется в ktlk. Эти преобразованные слова я называю метафонами.
Но, укорочение делается не всегда. Во-первых, там можно настроить, указав, до скольки символов сокращать слово-метафон. Во-вторых, эта нередко функция сама решает, сколько требуется символов, даже если указать ограничение.
6. Далее удаляются неуникальные (в пределах каждого файла) слова-метафоны. По идее, можно было бы при таком удалении присваивать им вес - частоту вхождения. С тем, чтобы потом ранжировать файлы по частоте встречаемости тех или иных слов (и тогда получится авторская поисковая система, типа раннего Яндекса или т.п.). Но, пока я не стал это делать.
Слишком короткие слова (из одного или двух символов) также удаляются. Цифры, специальные символы - тоже удаляются. Т.е. остаются только пробелы и английские (латинские) буквы.
7. А далее, собственно, сам процесс индексации метафонов. Здесь - два основных пути. Либо использовать файлы с надлежащей разметкой внутри них (в том числе, бинарные файлы). Либо - использовать "естественный" способ индексации, т.е. саму файловую систему операционной системы (это я сам придумал). Можно еще, конечно, использовать и базы данных. Но... "это" мне как-то неохота применять, по крайней мере, в силу высокой длительности запросов. Да и в силу уязвимостей. Для себя я такое использовать не буду. Вот для кого-нибудь, кто согласен, кому нравятся базы данных - на здоровье.
В общем, остановился на втором варианте. Как он выглядит?
Есть общий каталог под названием metaphones. В нем создаются каталоги с именами в 1 символ, сообразно символам того или иного метафона. Но, для двух ПОСЛЕДНИХ символов каждого метафона каталоги не создаются, эти символы записываются в файл под названием 1.txt. Например, для метафона frmy будет сформирован каталог с именем f, в нем будет каталог с именем r, а в этом каталоге будет файл 1.txt, в котором будет присутствовать строка my. Ну, за ней будут присутствовать индексы (см. п. 1) соответствующих файлов, из слов которых был получено этот метафон. Например, вот строка для указанного метафона:

Каждое число - это соответствующий индекс. Это значит, что данный метафон имеется в файлах, имеющих индексы 677, 9, 10, 34, ну, и т.д. Чисел этих может быть довольно много.
Относительный путь к файлу, в котором находится эта строка, имеет вид \metaphones\f\r\ 1.txt.
\metaphones\f\r\m\y.txt. А в этом файле присутствуют только индексы (числа). Но, как оказалось, эта система будет хороша, если файлов на сайте будет весьма много (от 100 тысяч). Вот тогда она покажет свои лучшие стороны. А в моем случае оказывается, что объем, занимаемый индексными файлами на диске, превышает объем исходных файлов (с расширениями txt, html) чуть ли не на ДВА порядка. Отчего? От того, что в Windows, даже если в файл записано всего лишь несколько байт, он все равно будет занимать на диске 4 кБ. Т.е.дисковое пространстов будет тратиться попусту. Другое дело, если индексов будет много и тогда в практически каждом индексном файле будет не несколько (десятков) байт, а большое объем индексов. Но, повторюсь, это (пока) не в моем случае.

Вот по этому алгоритму индексы слов-метафонов распределяются по файлам 1.txt, находящихся в соответствующих каталогах. И вот как оно примерно выглядит.
Конечно, таких каталогов будет множество, многие тысячи. НО, не сотни тысяч. Потому как русский язык - это достаточно ограниченная совокупность слов, хотя и очень большая. Но, все равно не бесконечен он. Поэтому рано или поздно, при увеличении числа индексируемых файлов (с контентом) новые каталоги больше не будут образовываться. Да и их общее число можно оценить теоретически.
Известно, что в английском алфавите присутствует 26 символов. Т.е. 26 (точнее, 27, если учитывать еще 0) каталогов верхнего уровня. В каждом из них может быть не более 27 каталогов второго уровня. И т.д., вплоть до 5-го уровня (число символов функции metaphone по умолчанию.
Тогда общее теоретическое максимальное число каталогов составит
271 + 272 + ... + 275 ≈ 1,5*107.
Т.е. 15 миллионов каталогов. Это - очень много. Но, это - при условии, что они будут присутствовать ВСЕ. Т.е. если в файлах на сайте будут представлены чуть ли не все слова русского языка. Хотя, даже в этом случае для современных компьютеров такое число каталогов, вроде бы, не проблема. В конце концов, можно сделать и распределенную систему, просто поместив часть каталогов на другой компьютер.
Впрочем, это - теоретическое число, на самом деле максимальное число файлов и каталогов будет гораздо меньше. Например, в русском языке есть слово "время", но нет слов "ревмя", "емярв" и т.п. Так, индексация слов из русского словаря ru.dic привела к созданию не более 14 тысяч каталогов. Их хватит, чтобы проиндексировать любые слова, имеющиеся в русском языке. Ну, а неправильных слов, типа "емярв", едва ли будет много. Поэтому фактическое число каталогов будет порядка на три меньше, чем теоретическое. Что, видимо, вполне приемлемо даже для не слишком мощного компьютера.
Что сказать? Одна тысяча файлов индексируется примерно за 10-15 минут. Т.е. за час будет индексировано примерно 5-10 тыс. файлов. НО - это у меня, на РНР 5. Да и компьютер у меня, скажем так, далеко не самый быстродействующий. Если же использовать РНР 8, то там быстродействие примерно в 10 раз выше. Ну, а если - еще и современный сервер, то вместо 10 минут получится несколько секунд.
Собственно, с индексацией закончено. Теперь требуется интерфейс для работы с искомыми ключевыми словами.
8. Далее - интерфейс. Например, требуется найти все файлы сайта, содержащие или одновременно два слова "время, каталог", или слово "мир". Т.е. получается логическое выражение:
время && каталог || мир
Эти ключевые слова транслитируются, затем метафинизируются по тому же самому алгоритму, что и при индексации. Для удобства, чтобы каждый раз не переключаться на латинские буквы, я сделал возможность использования русскоязычных логических операторов И, ИЛИ. Т.е. логическое выражение может быть записано и так:
время и каталог или мир
Или даже так, еще проще:
время каталог или мир
Также можно использовать круглые скобки, если для более сложного поиска.
В общем, эта фраза идет из браузера на сервер. Там она разбирается, анализируется, ищутся возможные ошибки (не знаю уж, насколько внимательно и полно я предусмотрел их обработку). Затем метафонизируется, получится вот что:
frmy&&ktlk||mir
9. Ну, а потом начинается поиск отдельных метафонов (frmy, ktlk, mir) в каталогах и файлах 1.txt, с учетом путей к ним (см. выше). Если соответствующая строка в каком-то из них есть, значит, все индексы, идущие, например, за метафоном frmy, обозначают индексы файлов, в которых присутствуют слова, метафон из которых соответствует frmy.
Сложно, но, что поделать. Это не на Laravel каком-нибудь ахинеей заниматься.
10. А дальше я придумал такой способ. Я заменяю метафоны в логическом выражении на соответствующие логические переменные. Пытаюсь найти файл 1.txt, в котором может присутствовать строка из 2-х последних символов метафона (например, строка my). Найти этот файл, если он есть, очень быстро. Нужно лишь взять путь вида \metaphones\f\r\ (где f, r являются первыми символами метафона) и добавить к нему 1.txt. Проверка наличия/отсутствия такого файла делается практически мгновенно. Это зависит не столько от языка PHP, сколько от быстродействия (ядра) операционной системы. Уж быстрее едва ли что есть, разве что Assembler.
Если такой файл имеется, также если последняя часть метафона (его последние 2 символа) присутствует этом файле, то считываем соответствующую строку (вида my|;677;9;10;34;48;. В ней присутствуют индексы файлов сайта, в которых имеется ключевое слово, метафон которого имеет вид frmy.
И вот тогда в логическом выражении для конкретного индекса (например, для индекса 677) вместо метафона я подставляю логическое значение true (1).
Иначе - false (0). Это если либо нет такого файла, либо в таком файле нет подобной строки, начинающейся с my.
Аналогично поступаю для других искомых ключевых слов.
В результате, вместо
время && каталог || мир
получается что-то вроде
1 && 1 || 0
Это тогда, когда слова время, мир присутствуют в некоем файле, имеющем индекс, например, 677; а вот слова мир в этом файле нет.
11. Таким же образом анализируются все остальные индексы, для каждого из искомых ключевых слов. Например, для слова каталог (метафон ktlk) в индексном файле 1.txt имеется строка с соответствующим множеством индексов:
lk|;677;11;30;256;
Поэтому для индекса 677 это слово в логическом выражении будет заменено на true.
В итоге, для каждого индекса (из множеств индексов) составляется логическое выражение.
12. Ну, а получающееся логическое выражение, для каждого индекса, оценивается при помощи функции eval. Если оно дает результат true, значит, искомые ключевые слова, с учетом данного логического выражения, имеются в файле сайта, имеющем соответствующий индекс (в данном случае, 677). Если получилось false, значит логическое выражение, содержащее искомые ключевые слова, не выполняется для этого файла.
13. И, наконец, дело за малым: найти файлы, соответствующие "удачным" индексам (т.е. таким, которые соответствуют файлам сайта, для которых выполняется логическое выражение с ключевыми словами). Это - легко и быстро, т.к. соответствующей перечень имеется в файле files.txt.
14. Напоследок, нужно вывести найденные имена "удачных" файлов на экран. Выводить частями, например, по 30 имен за раз.
Так что вот, вполне можно обойтись без использования баз данных
Ибо делается-то ОДИН раз. Даже лучше, чем базы данных, мне так кажется.
Не знаю, возможно, в каких-нибудь базах данных есть еще более экономичные и быстродействующие решения.
О качестве поиска
Качество хорошее, если говорить о поиске с точным соответствием. Если файлы с контентом проиндексированы, то поиск выполняется очень быстро.
А вот если говорить именно о нечетком поиске, то качество, конечно, не идеальное. Т.е. вместе со словом "время" найдутся, например, слова "фремя". Вместе со словом "результат" найдется слово "резульдад". И т.д. Т.е. поиск именно по схожести звучания (произношения), а не синтаксически близкие.
Однако, с другой стороны, если искать слово "статья", то НЕ найдутся слова "статье" или "статьей"; слово "стать" тоже НЕ найдется. Однако, будет найдено слово типа "статьяааоо". Однако, слово "статьяааоой" найдено НЕ будет. Т.е. сходство слов не совсем очевидное.
Вероятно, от метафонизации придется уйти в другое решение. Пока думаю об использовании готового словаря русского языка. В частности, словаря, используемого браузером Firefox. Идея в том, что, например, если ввести ключевое слово "бежать", то будет осуществляться также поиск слов "бежит", "бегут", "бежала" и т.д. ОДНАКО(?), как быть, если при поиске ввести слово "бежит"? Как при использовании словаря искать те же самые слова ("бежать", "бежали", "бежала" и пр.)? Пока так и не пойму...
Кстати, насчет искусственного так называемого "интеллекта"
Открыв файлы словаря русского языка от браузера Firefox (в частности, файл ru.aff), я обнаружил там нечитаемые слова, типа ЪБФШУС, ЦХФУС. Я-то не знал, что это такое. Смотрел в интернете - не нашел. Потом спросил яндексовую Алису. Детально, подробно сформулировал вопросы. Она отмечала, мол, кодировка неправильная, убедитесь, что эти файлы
закодированы в правильной кодировке и должна быть, мол, UTF-8. Причем,
про эту кодировку она несколько раз отвечала очень убежденно.

Для таких случаев есть выражение: "лютая дичь".
Хорошо хоть, Алиса дала ссылки, на основе которых формулировала свой "ответ". Эти ссылки оказались более полезными.
И если, мол, эти файлы у вас не в этой кодировке, то это, типа того, ошибка, перекодируйте их в UTF-8. Или замените их на правильные файлы. ВСЕ, более я ничего по сути от нее добиться не смог.
Поначалу мне подумалось, что, может, это каким-то хитрым образом закодированы русские слова... ну, типа как я сам реализовал систему индексирования на основе закодированных метафонов при помощи имен каталогов операционной системы.
Кодировка... Уже потом, на одном онлайн-сервисе проверки кодировки, путем подбора, выяснил, что это, оказывается, просто-напросто русские слова в кодировке KOI8-R, но распознанной как Windows-1251. А UTF-8 тут вообще ни причем. И уж потом в этом файле, в самом, начале я вдруг обнаружил SET KOI8-R (Алиса об этой строчке, естественно, помалкивала). Видимо, это и есть обозначение кодировки этого файла (которую я вначале определил эмпирическим путем).
AI.Почему? Потому, что они настолько недоразвиты, настолько ограниченные и беспросветные, что на их-то фоне даже искусственный интеллект является как бы умным и знающим, поэтому (и только поэтому) они и восторгаются им. И готовы применять его везде. НО: это только на их фоне, на фоне этих разных многочисленных недорослей и дураков. Их уже ничего не вылечит, туда им и дорога.
Это касается "таких" и рядовых программистов, и их руководство. А всем остальным скажу так: БОЖЕ УПАСИ доверять искусственному интеллекту мало-мальски серьезную работу. Иначе он ТАКОЕ натворит, что потом годами придется расхлебывать. Например, в моем случае искусственный интеллект перекодировал бы файлы русского словаря в кодировку UTF-8 и тем самым сломал бы проверку русской орфографии в браузере, сделав ее не работающей.
Ну, а те, кто пытается внедрить искусственный интеллект еще и в систему обучения - это, на мой личный взгляд, тяжкие государственные преступники и/или полностью сумасшедшие, с медицинским диагнозом не ниже "дебил". Этих людей (всех таких, не делая скидок) следует изолировать от общества. Особенно тех, кто обладает высокой властью.
Вот она, эта программа.
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
Версия 2.0
В этой версии я реализовал нечеткий поиск (пока - только для русского языка). В планах - сделать и английский поиск, аналогично. Т.е. это поиск слов с разными окончаниями. Например, обычным поиском будет найдено слово "работа". Тогда как нечеткий поиск найдет соответствие следующему логическому выражению:
(работа || работе || работой || работою || работы || ...)
То же самое - для глаголов, прилагательных. Если обычный поиск длится, скажем, десятую долю секунды, то нечеткий длится 3...15 секунд, в зависимости от слова. Т.е. нечеткий поиск длится дольше на 1-2 порядка. Но, зато находит слова, примерно как Яндекс или Google. Долгий он из-за длительной (надеюсь, временно) работы со словарем.
Как сделал
Разумеется, уже давно кем-то используются готовые алгоритмы. Я с ними не знакомился (даже не смотрел вообще), решил сделать полностью по-своему.
Собственно, использовал предыдущую версию своей программы, но к ней добавил работу со словарем русского языка. Взял я его из браузера, 36-го Firefox.
Эти файлы состоят из ru.dic (собственно, словарь русского языка) и ru.aff - словарь суффиксов (точнее, окончаний). Словарь нужен для того, чтобы найти всевозможные формы слова, с учетом окончаний. В частности, с учетом времен, падежей, родов, определенной/неопределенной форм (для глаголов), страдательного залога.
Так вот, получение этих всевозможных форм для одного слова у меня составляет примерно 3-4 секунды. Это, конечно, очень много. Ведь в том же Firefox проверка грамматики (в режиме contenteditable) делается практически мгновенно.
Причина (пока) в том, что файл словаря - довольно большой, там около 150 тыс. строк - слов. Так вот, перебор в этом словаре занимает много времени. Проблема еще в том, что слова там расположены вообще не по алфавиту. Поэтому поиск, например, методом бисекции (начиная с середины файла, потом половину делим еще на две части и т.д.) не получится. Не знаю, почему там не по алфавиту. Может, так задумано зачем-то.
Видимо, для ускорения работы придется как-то индексировать этот словарь. Пока даже не пойму, как именно. Вероятно, можно использовать ту же самую, мою систему индексации, в каталоге metaphones. Только имена файлов сделать не 1.txt, а, например, 1.dic. Если так сделать, поиск будет практически мгновенным даже при гораздо большем объеме словаря. А расход дисковой памяти возрастет несущественно. Оперативной памяти - также довольно мало расходуется.
Единственная проблема - это то, что образуется множество (теоретически - сотни тысяч, даже миллионы) вложенных каталогов. Что интересно, их удаление из корзины Windows занимает достаточно времени; даже при том, что фактически их объем может составлять, скажем, 2-3 МБ. Однако, пока они спокойно находятся на жестком диске, особых проблем нет. Все-таки, для системы NTFS это - мелочи (в отличие от FAT).
Еще, конечно, думается, может, сделать индексацию частично по каталогам, а частично - как в базах данных (точнее, в их движках). Так можно будет достичь того, что и число каталогов будет равным "всего лишь" несколько десятков тысяч; и скорость поиска будет приемлемой.
1. Идея - использовать в индексных файлах разметку. Типа, как на "кластеры".
А чтобы они не занимали на диске много места - архивировать их и каждый раз разархивировать (сразу в оперативную память), если возникла потребность в именно конкретном файле. Ведь при поиске потребуется, быть может, лишь несколько файлов, а не все. Остальные будут спокойно лежать архивированными и не занимать много места. Тем более, есть в РНР функции работы с архивами, например, с файлами gz. Правда, пока я ними не работал.
2. Или, как вариант, возможно, читать словарь в массив, делать некие маркеры, разделяющие группы слов по алфавиту (для этого придется таки отсортировать словарь). Чтобы массив НЕ перебирать по одному элементу, а сразу переходить к нужной группе слов, исходя из ее маркера-индекса. Перечень этих маркеров тоже хранить в каком-то (тоже индексном) файле. Правда, если для этого использовать двумерный (не одномерный) массив, это может замедлить работу. По идее, если организовать 100 маркеров, получится ускорение поиска в словаре примерно в 100 раз. Но, для этого придется отсортировать словарь.
3. А то и, как вариант, реализовать ТУ ЖЕ САМУЮ структуру, по типу структуры каталогов, только среди элементов массива. Получится, конечно, многомерный массив. Например, нужно найти слово "каталог" (метафон - ktlk). Пусть массив имеет имя М. Тогда, условно, $М['k']['t']['l']['k'] = 'K'. Это будет соответствовать следующей строчке из словаря: каталог/K. Если же такой строчки не было бы, то этого элемента массива тоже не было бы. Очень удобно, быстро и просто. Однако, когда я опробовал этот вариант, то в итоге массив занял 130 МБ с лишним, после чего РНР прекратил работу. Т.е. этот вариант, видимо, оптимален для достаточно мощных компьютеров (типа Яндексовских или Google-ских). Но, не для меня. Мне пришлось использовать другой вариант.
Вот она, версия 2.0.
Версия 2.1
Решил я пока остановиться на 2-м варианте, с маркерами. Содержимое файла-словаря отсортировал и перевел в нижний регистр. Честно говоря, обычные функции PHP работают на порядок быстрее, чем те, что не учитывают регистр (с символом i).
Почему такой вывод? Потому, что делал соответствующие эксперименты. Так вот, время, затрачиваемое на преобразование в один регистр и последующий поиск обычной функцией, примерно равно времени на поиск безрегистровой функцией.
И едва ли будет потребность в поиске с учетом регистра (исключение составляет поиск неких особенных одиночных слов). Но, для этого у меня давно сделан другой поиск. Он - без индексации, поэтому на порядок дольше.
Вот что примерно получается в файле маркеров:
ак:1-1424:1424:7-34382
ал:1425-3876:2452:34382-91916
ат:3877-4211:335:91916-99345
бк:4212-6956:2745:99345-162462
бл:6957-8820:1864:162462-203453
бт:8821-9570:750:203453-218549
вк:9571-12949:3379:218549-293380
вл:12950-16817:3868:293380-385128
вт:16818-20933:4116:385128-485841
...
Объем этого файла занимает 3 кБ с лишним, что вполне даже приемлемо. Меньше 4096 байт (с учетом того, что работаю в Windows, где размер файла на диске составляет на менее указанного числа байт; на моем компьютере, по крайней мере).
- Маркер "ак" соответствует тем файлам из словаря, первые буквы которых не превышают "ак". Т.е. это слова "акт", "активно", "аист", "адовый" и т.д.
- Маркер "ал" соответствует тем файлам из словаря, первые буквы которых выше "ак", но не превышают "ат". Это слова, типа "алый", "аметист", "априори", "атрофированный".
- Маркер "ат" соответствует тем файлам из словаря, первые буквы которых выше "ат". Это слова, типа "афера", "аутодафе", "ахинея".
Последние цифра справа - это номера (диапазоны номеров) позиций (в байтах) в отсортированном файле-словаре (ru.dic), на которых содержатся соответствующие слова. Например, слова из первой вышеупомянутой группы находятся на позициях из диапазона 7-34382.
Слева от этих цифр находятся, числа, указывающие количества строк (слов) в файле-словаре, соответствующие указанному маркеру (в начале строки). Например, для буквы "а" они составляют: 1424, 2452, 335. Числа различаются примерно на порядок. Т.е. время поиска будет самым коротким для слов, соответствующим маркеру "ат", а самым длинным - для маркера "ал". В целях оптимизации, возможно, следует подобрать другие буквы. Например, "и"("к"), "п" (вместо "к"("л"), "т").
Однако, даже и сейчас, в результате, если ранее (в версии 2.0) поиск слова в словаре составлял секунды 2...3, то теперь - 0,18 секунд. Теперь это время уже не является лимитирующим. Лимитирующее время (0,23 сек.) тратится на поиск имени файла и получение тех имен файлов, которые имеют индексы, содержащиеся в том или ином файле с именами 1.txt (расположенных в каталогах metaphones). Вероятно, это потому что пока он читается построчно. Возможно, если читать его и сразу преобразовывать в массив (по частям, для больших файлов), это время снизится примерно на 1-2 порядка.
Если точнее, оказалось, что при использовании массива время составило всего лишь (примерно) 0,006 секунд. Т.е., как и ожидалось, экономия времени составила в 30 раз. Т.е. даже если файлов на (локальном) сайте будет не пара тысяч, а миллион, соответствующее время будет 6 секунд... что в целом, приемлемо. Ну, а если еще и здесь сделать индексирование, то время снизится на 1-2 порядка. Так что возможности, пожалуй, практически неограниченные, даже на моем компьютере, даже на PHP 5. Ну, а уж на сервере, даже на сравнительно маломощном хостинге, поиск будет и вовсе "летать", т.е. выполняться практически мгновенно. Никакие базы данных, как мне видится, не угонятся (здесь я могу ошибаться).
Т.е. даже для больших файлов есть смысл все-таки использовать массив, пусть даже по частями.
Также немало времени занимает блок оценки логических выражений при помощи eval - целых 0,12 сек. Возможно, там тоже надо что-то оптимизировать.
Что сказать? Общее время на поиск слов (одного или нескольких, с учетом логического выражения) составляет, на моем компьютере, примерно 0,6 секунд; ну, плюс-минус. Среди примерно 1000...2000 файлов (txt, html). Общий их объем составляет где-то 30 МБ.
Кстати, программа PhpStorm при каждом запуске производит индексирование файлов (для сайтов, которые есть на сервере). Это дает возможность быстро найти любое слово/фразу. Любопытно, что время, затрачиваемое ею на индексирование, - примерно такое же, как и у меня, т.е. несколько секунд (максимум, секунд 10). Хотя, PhpStorm написана на java+Kotlin, а у меня тут - PHP. Время на поиск - примерно такое же, но, пожалуй, пониже.
Так думаю, задержка связана с тем, что, во-первых, РНР 5, а, во-вторых, - что Denwer. Если же это все запустить на PHP 8 и без Denwer, то скорость возрастет примерно на порядок. Возможно, будет работать быстрее, чем PhpStorm. Правда, там можно искать ЛЮБОЕ слово, в том числе, содержащее спецсимволы, английские буквы и цифры.
В базах данных, вероятно, при индексировании поиск осуществляется за удовлетворительное время даже при большом их размере, в гигабайты, десятки, сотни гигабайт. Однако, цена тому - высокая избыточность в расходовании дискового пространства.
mb_check_encoding(). В частности, она стала способна "определить" кодировку текста как cp1251, хотя на самом деле текст закодирован в utf-8. Тогда как РНР 5.3 таких проблем нет.Хммм... А не за счет ли подобных ухудшений в новых версиях РНР повысилось быстродействие??... Так я посмотрю-посмотрю, да и останусь на старом-надежном РНР 5.3...
Вот она, версия 2.1.
Версия 2.2
Как я решил решить проблему слишком длительного поиска слов и их суффиксов в файле-словаре. Вначале все раздумывал о некоей функции, которая сопоставляла бы словам те или иные (большие) числа. Может, даже сделать аналитическое представление ее и потом быстро вычислять (приближенные) номера позиций соответствующих слов, в зависимости от букв, содержащихся в слове. Что-то типа корреляции-регрессии. Для ускорения поиска можно было бы применить какой-нибудь метод Ньютона-Рафсона... Но, это придется хранить информации не намного меньше, чем сам объем словаря. Как-то не очень. Да и, честно говоря, если говорить о функции, то там будут коэффициенты, члены. А операции сложения, не говоря уж об умножении и делении - достаточно длительные. По сравнению с логическими. Быстрее выполнить несколько десятков логических операций сравнения (в процессе перебора), чем одну операцию умножения.
Еще мне думалось применить метод бисекций. Т.е. взять половину массива содержимого словаря; затем от той части, которая, возможно, содержит искомое слово, опять взять половину. И т.д. До тех пор, пока не останется 5-10 строк (а в них уже искать прямым перебором). Всего таких делений потребуется не более 15. Операции эти будут делаться очень быстро, т.к. это - всего-навсего перемещение указателя в файле-словаре (точнее, в соответствующем массиве)... Впрочем, перемещение указателя в файле будет тоже быстрым. Т.е. даже необязательно читать его в массив.
Решил я в итоге проиндексировать словарь. Индексацию делать в тех же самых файлах 1.txt. Чтобы не плодить лишние файлы. Да и для удобства. Точнее, в них оставлять признаки того, что соответствующие слова (с учетом их метафонизации) присутствуют в файле-словаре. Примерно так:
lk:1|;677;11;30;256;
Здесь символы :1 указывают на факт присутствия, если у словарного слова нет индекса. Если в словаре этому слову задан суффикс (например, BL), то будет выглядеть примерно так:
lk:/BL|;677;11;30;256;
Ну, а если нет такого слова в словаре (метафон которого заканчивается на две последние буквы lk), значит, будет по-старому, как обычно:
lk|;677;11;30;256;
Это - достаточно длительная процедура. Поэтому проводить ее лучше не при индексации содержимого файлов сайта, а отдельно, в процессе просмотра всех слов словаря, его сортировки и, как результат, установки признаков присутствия в соответствующих файлах 1.txt. Можно даже заранее провести ее, а потом уже делать индексацию файлов. Я ее (пока) совместил с процедурой индексирования имен файлов.
Т.е. теперь при определении, содержится ли конкретное искомое слово в файлах сайта (с учетом его разных грамматических форм и сходного звучания), уже нет необходимости каждый раз просматривать файл русского словаря (состоящий из почти 150 тыс. строк) для определения, содержится ли там соответствующее слово, а также для определения его суффикса. Теперь для этого достаточно (очень быстро) открыть соответствующий файл (если он есть) и найти там строку, начинающуюся с lk. Если за ней имеется признак присутствия - значит, слово есть в словаре (с индексом или без оного). Если такого признака нет, или нет строки, начинающейся с lk, или вовсе нет такого файла - значит, слова в словаре нет.
В результате, время, требуемое для получения массива всевозможных форм слова (с учетом окончаний из файла ru.aff), снизилось примерно в 3-5 раз. Не на порядок, конечно, но хоть так. Сейчас оно у меня составляет где-то 0,12 секунд. А для 10 слов (в составе логического выражения) соответствующее время составит где-то всего лишь 0,2 сек. А не 1,2 сек. Дело в том, что это время, в зависимости от числа искомых слов, увеличивается нелинейно, причем, чем больше слов, тем слабее тем прироста времени. Т.е. зависимость довольно слабая, типа логарифма. Удивительно, но факт. Видимо, это связано с кэшированием функций в языке PHP. Одно дело, когда функция вызывается для получения разных грамматических форм одного слова, только один раз. Другое дело - когда для 10 слов она, уже будучи кэшированной, вызывается 10 раз подряд.
Основная проблема: почему-то иногда создаются заведомо неверные грамматические формы, наряду с верными. Пока я так и не понял, почему. Для некоторых слов. А для каких-то слов получается слишком много форм. Например, для слова "поле" вот какие формы получаются:
поле, полями, полям, полях, поля, полей, полю, полем, пол, полу, полом, пола, поль, поло, полы, полой, полою, полами, полам, полах.
Т.е. одновременно идет речь о таких начальных словах, как "поле", "пол", "пола". Ибо программа не понимает смысла. Для нее слово "поле" - это именительный падеж слова "поле", предложный падеж слова "пол", дательный/предложный падежи слова "пола". Да еще добавлены несклоняемые слова "поло", "поль".
А вот если искать слово "полями", то будет:
поль, поля, полем, поле, полю, полями, полям, полях, полей
Поэтому для поиска именно слов типа "поле" в именительном падеже (в смысле - равнина, пшеничное поле) есть смысл искать его не в начальной форме (именительный падеж единственного числа), а в каком-нибудь падеже. Возможно, для этого стоит сделать функциональность сужения поиска путем задания нескольких падежей слова. Например, если искать в режиме НЕЧЕТКОГО поиска:
(поле и полями и полей)
то чтобы программа искала те и ТОЛЬКО те словоформы, которые одновременно соответствуют каждому из трех слов. Так слова "пол", "пола" и их формы, не относящиеся к указанным трем словам, будут исключены. Так можно существенно уточнить поиск, наделить его зачатками смысла. И легко обойтись без искусственного интеллекта.
Попутно, Яндекс, а, может, вы, вместо своих турбо-страниц (это, за редким исключением, - излишняя и ненужная вещь), ВНОВЬ вернете "Оригинальные тексты"? А то ведь по мало-мальски частым запросам найти-то уже практически ничего невозможно. В выдаче присутствуют многие десятки клонов. Например, попробуйте САМИ рискнуть и потратить многие часы времени на поиск словосочетания "ремонт карбюратора". Ведь ваша система выдаст просто кучу страниц, дублирующих друг друга, зачастую, дословно. А полезной-то информации - или нету, или она где-то там, внизу выдачи, далеко. Раньше же, когда был сервис "Оригинальные тексты", такого не было все же или, если и было, то далеко не в том объеме, как сейчас (2025-2026 гг.).
Тут надо определиться, ЧТО именно вы хотите выдать пользователю. Если это - какой-нибудь "крутой" сайт, не взирая на полезность его контента, то это одно. Лично мне такие сайты не нужны совсем, их ценность имеет сильно-ОТРИЦАТЕЛЬНУЮ величину. А если именно ПОЛЕЗНЫЙ КОНТЕНТ - тут же обязательно необходимо учитывать авторство. Ведь не зря во всем мире принят институт права, в том числе, права авторства и права собственности. Это же не просто так, не только даже в угоду авторам или владельцам имущества. А ради отсеивания плагиаторов, чтобы не плодить мусорный контент. И чтобы полезный контент мог развиваться.
Общее время поиска снизилось где-то на 0,2-0,3 сек., составив 0,3-0,4 сек.
Версия 2.3
Однако, все еще осталось лимитирующее время на поиск имени файла и получение тех имен файлов, которые имеют индексы, содержащиеся в том или ином файле с именами 1.txt. Конечно, в предыдущей версии я определенно снизил это время. Но, оно все равно (было) высокое. Поэтому решил - поменять алгоритм поиска имен файлов.
Раньше я, для множества найденных индексов файлов, оценивал (в цикле) каждый индекс на предмет истинности/ложности логического выражения, состоящего из искомых слов. Истинно, когда слова входят (хотя бы один раз) в файл, имеющий такой индекс; причем, входят ТАК, что логическое выражение дает true. Ложно - в ином случае. Видимо, это длилось достаточно долго.
Поэтому я решил изменить алгоритм. Теперь для каждой формы (в случае НЕчеткого поиска) слова я нахожу соответствующие ему индексы. Т.е. - массивы индексов файлов, в которых это слово присутствует. Т.е. это слова типа "поле", "поля", "полем" и т.д. каждому из этих слов соответствует то или иное множество индексов.
А ПОТОМ я делаю объединение и нормализацию таких массивов. При этом дублирующие индексы исчезают.
Для того, чтобы упростить такие операции (точнее, их синтаксис), я написал функцию array_logical. Ну, чтобы каждый раз в программном коде не прописывать команды array_merge(). Итак, для каждого искомого ключевого слова формируется соответствующий ему массив индексов, с учетом словоформ этого слова. И так - для каждого искомого слова.
Например, ищется следующее логическое выражение:
слово1 И слово2 ИЛИ слово3
Или иначе:
слово1 && слово2 || слово3
Допустим, слово1, в том числе, его возможные словоформы, встречается в файлах сайта, соответствующих индексам 1, 2, 3, 6. Тогда как слово2 встречается в файлах сайта, соответствующих индексам 2, 8, 11.
А слово3 встречается в файлах сайта, соответствующих индексам 3, 9, 10. И мы хотим найти такие файлы сайта, в которых содержатся одновременно ОБА слова слово1, слово2, и/или же в них содержится слово3.
В итоге получится примерно такое логическое выражение:
array(1, 2, 3, 6) && array(2, 8, 11) || array(3, 9, 10)
Символы "&&" обозначают пересечение индексов, а символы "||" - объединение. Т.е. можно, конечно, реализовать соответствующую логику. Но, я пошел более наглядным, как мне кажется, путем: на базе функции eval. При помощи функции array_logical я делаю логические операции между массивами. Например, результатом указанного выше логического выражения будет:
array(2, 3, 9, 10)
Это - массив тех самых индексов, которые соответствуют присутствию искомых ключевых слов в файлах (соответствующих этим индексам) сайта с учетом заданного (при поиске) логического выражения.
Так вот, при таком подходе общее время нечеткого поиска снизилось еще сильнее и достигло примерно 0,2 секунды (для 1...2 тысяч файлов общим объемом примерно 30 МБ) при запросе от одного до 4...6 слов, в составе логического выражения. Это - в РНР 5.3. А если взять более новый РНР, например, 8.0, то там время поиска будет еще меньше. Все-таки, операции объединения/пересечения элементов массивов выполняются гораздо быстрее, чем когда для каждого индекса (в цикле) делать перебор и определять, соответствует ли он, этот индекс (точнее, файл, имеющий этот индекс), искомому логическому выражению или нет.
И, кроме того, программный код упростился.
Кстати, если объем файлов (и их число), увеличить гораздо более, скажем, на 1-2 порядка, до 100-300 тыс. файлов, то время поиска вырастет несильно. Потому что поиск будет, как и сейчас, осуществляться, в первую очередь, по каталогам файловой системы (сервера). Это - очень быстрые операции. При том, что число каталогов и файлов в каталоге metaphones вырастет несущественно. Едва ли некоторые файлы 1.txt накопят существенное количество строк. Почему? Да, потому, что число словоформ в любом языке, в том числе и в русском, не только конечно, но и достаточно ограниченно.
Ну, даже не знаю... едва ли возможно снизить время поиска еще дополнительно. Разве что, наиболее затратную по времени процедуру (поиск разных словоформ для каждого ключевого слова, программа keywords_FINDER_fuzzy.php) переписать на С/С++ и вызывать ее из РНР.
Как мне видится (могу ошибаться), получилась отличная процедура поиска вместо использования баз данных. Потому как выборка из баз данных даже при наличии индексации делается медленнее, даже на достаточно мощном сервере. Который мощнее моего компьютера, как минимум, на порядок, а то и больше. При том, что эта моя процедура будет работать не намного медленнее при существенном увеличении числа файлов. Даже для большого общего объема файлов, в которых производится поиск. По моим оценкам, программа будет работать вполне уверенно при числе индексированных файлов, как минимум, до 200-500 тысяч даже на компьютере средней мощности (по нынешним меркам). При достаточно большом числе файлов может возникнуть проблема с массивами. Возможно, придется делать отдельную логику для работы в массивами по частям. Но, пока у меня такой задачи нет.
Вот только индексация длится достаточно медленно даже для указанного числа файлов. Например, если число файлов будет составлять 20 тысяч, их общий объем - 300 МБ, то индексация на моем компьютере займет примерно день. А время поиска будет, вероятно, примерно таким же.
Впрочем, на сервере ее можно периодически запускать ночами (индексировать по частям).
Вот она, версия 2.3.
Версия 2.4
В предыдущих версиях в процессе индексирования ФАЙЛОВ сайта браузер, по сути, не закрывал соединение и ждал, пока сервер не пришлет ему все имена файлов, которые он проиндексировал. Отправка имен браузеру шла постепенно, по мере индексации.
Если делать это уже после полной загрузки страницы, обычным AJAX (или AJAJ), то такое - невозможно, т.к. сервер буферизует данные и отправляет их сразу, одним сообщением. Т.е. это приходилось бы устраивать множественные запросы-ответы AJAX, чтобы давать информацию браузеру о ходе индексирования. Ну, либо как у меня использовалось - страница недозагружалась и такое длилось до тех пор, пока не закончится индексация (пусть даже многие часы). Это - как-то не очень, хотя вполне работало. Можно использовать для этой цели фрейм.
Можно, конечно, использовать вебсокеты. Очень удобная технология. Но, ради них придется держать открытым сервер сокетов. Можно, но как бы сказать...
Есть, к примеру, также PUSH уведомления (на которые можно подписаться), тоже удобно; но в хороших браузерах (например, в Firefox 24, 36) они не работают. Если говорить о Firefox, то там они доступны только с 44-й версии.
Новые браузеры, условно, подойдут в качестве открывалок некоторых современных вебресурсов, разработчики которых (видимо, в сговоре с производителями браузеров) не пожелали сделать их качественными и доступными для всех. Но, не более того. Работать с ними серьезно и постоянно - нет, боже упаси.
Поэтому я решил использовать события сервера. Это - вполне себе старая-добрая технология. Если при AJAX реализуется модель "запрос-ответ", то в модели событий сервера реализуются "один запрос - много ответов". Собственно, это - как раз то, что нужно. Направляется один запрос на индексацию (а на самом деле - запрос на установление связи на прослушивание событий сервера). После чего сервер, проиндексировав очередной файл сайта, выдает сообщение об этом браузеру. А браузер - ничего не делает, он только ждет сообщения (одно за другим), принимает их и отображает на экране, информируя пользователя о ходе индексации. И так длится до тех пор, пока индексация не прекратится.
Это избавило от необходимости, с одной стороны, держать страницу, открытую в браузере, недозагруженной, до момента окончания индексирования. И, с другой стороны, - от необходимости множественных AJAX-запросов.
Вот только индексация длится, конечно, долговато. Особенно, если файлов на сайте - много. Конечно, делается она один раз. Потом можно лишь доиндексировать, т.е. делать индексацию только более новых файлов. Но, тем не менее.
Интересно, что при запуске PHP 8.0 из консоли в связке с сервером IIS Windows события сервера SSE не работают, как положено. В частности, пока не завершена обработка событий, из браузера с той же страницы, с того же домена невозможно направить какой-нибудь другой запрос на сервер, невозможно даже обновить страницу. Я поначалу даже подумал, что это - "вина" разработчиков браузеров, но потом проверил тщательнее и убедился, что это не так.
Вот она, версия 2.4.
Версия 2.5
Теперь бы надо сделать сделать учет времени индексированных файлов. Дело в том, что если на сайт периодически добавляются новые файлы или изменяется содержимое старых, уже имеющихся, то они не попадут в индекс, пока не будут внесены в список файлов сайта, пока им не будет присвоен уникальный индекс и пока они не будут, собственно, проиндексированы. Не будешь же каждый раз индексировать ВСЕ файлы сайта заново. Лучше бы сделать функционал, в том числе, и на основе постепенного добавления.
Не знаю, насколько это будет эффективное решение. Но, пока подумываю сделать примерно так. В каждую строку файла-перечня ВСЕХ файлов сайта (которые предназначены для индексирования) добавлять еще и временну́ю метку UNIX. Т.е. какая-нибудь строка из этого файла-перечня будет иметь примерно такой вид:
\index.html|1660338149;9
Ну, а потом сравнивать эту метку с текущей меткой времени конкретного файла, в данном случае - index.html. Если текущая метка файла больше, чем сохраненная, значит, в нем делались изменения (или это вообще новый файл) и есть смысл его индексировать заново. А если метки равны, значит, файл не менялся и его индексировать не нужно.
А с другой стороны, может, лучше будет делать слегка измененную карту сайта, с учетом индексов файлов. Например, что-то типа:
<url index="9">Тогда не будет файла-перечня, по сути, дублирующего карту сайта. Правда, неизвестно, как к этому отнесутся поисковые системы. И, кроме того, может случиться так, что некоторые файлы, которые необходимо индексировать (например, технические) будут отсутствовать в карте сайта.
<loc>http://www.example.com/file.html</loc>
<lastmod>2025-03-05T03:09:16+04:00</lastmod>
<priority>0.2</priority>
</url>
Если метка равна нулю, значит, этот файл еще вообще не индексировался ни разу.
После каждой очередной индексации этого файла, если она прошла успешна, обновлять эту метку.

Как ни прикидывал, вроде бы, теоретически, это - оптимальный вариант. Хотя, конечно, это - дополнительные 10 байт хранящейся информации на каждое имя файла. Скажем, если имен файлов будет 1 миллион, понадобятся дополнительные примерно 10 МБ дисковой памяти только под эти метки. Впрочем, с другой стороны, там, где хранится аж миллион файлов, наверное, и дисковое пространство - соответствующее, большое.
Хотя, на практике такой подход оказался весьма ресурсоемким, медленно работающим. Дело в том, что получается массив вида
['filename1.html;1741206927|3',
'filename2.html;1716806902|4',
...
]
Чтобы извлечь метки времени, приходится делать цикл (array-map или еще что). Чтобы вначале определить, содержится ли в строке, являющейся элементом массива, та или иная метка времени. Это - довольно долго, если элементов массива много. Т.к. нужно не просто взять значение из элемента (или индекса), а анализировать, присутствует ли оно в нем.
Гораздо быстрее будет, если создать два массива:
[3 => 1741206927,
4 => 1716806902,
...
]
['filename1.html' => 3,
'filename2.html' => 4,
...
]
Наверное, примерно такая же технология работает в БД (базы данных), на низком уровне. Когда речь идет о связанных таблицах данных.
Однако, это - увеличенный расход памяти.
В идеале бы, конечно, сделать добавление строк в файл-перечень автоматически, при появлении новых файлов или при изменении уже имеющихся. Однако, это возможно, только если файлы на сайт будут добавляться лишь по протоколу НТТР (HTTPS), в том числе, например, при помощи моего редактора. А ведь они могут также добавляться и по FTP, и по SSH, например. Также они могут переименовываться, в том числе, и через панель хостинга. Поэтому, автоматически, в общем случае, не получится. К сожалению, по той же причине не получится и запасать имена новых (и/или измененных) файлов, чтобы потом заниматься индексированием только их. Видимо, придется, при очередном индексировании, каждый раз заново формировать файл-перечень. А вот потом, исходя из сравнения меток UNIX, индексировать только новые или измененные файлы. По идее, все равно будет хорошая экономия времени, чем если индексировать весь сайт заново.
И, к сожалению, вроде бы, отсутствует быстродействующая функция (как в РНР, так и в Windows, Linux), способная определять каталог как измененный (и дату такого изменения), если какой-либо имеющийся в нем файл был изменен. Вот если файл добавить или удалить - тогда каталог будет считаться измененным. А если модифицировать - то нет.
В итоге, я сделал так. Допустим, когда-то был сформирован файл-перечень, содержащий имена и индексы проиндексированных файлов сайта. Т.е. примерно такой файл:
\fileName;timeUNIX:1771967803|index
\123__.php;1771967803|0
\footer.html;1741206927|1
\index.html;1763002869|2
\left.txt;1716806902|3
\right.txt;1716805583|4
\top.txt;1716805672|5
После этого на сайт были добавлены другие файлы. До тех пор, пока их имена не добавлены в файл-перечень, пока их содержимое не проиндексировано, они не будут участвовать в поиске.
Так вот, я добавил функцию "доиндексирования", т.е. индексирования не всех файлов, а только тех, которые были добавлены недавно, после очередного индексирования и создания файла-перечня. Для этого в первую его строчку записывается максимальная метка времени UNIX, для перечисленных в нем (имен) файлов. При дополнительной индексации снова, как обычно, просматриваются все каталоги, не запрещенные к индексированию. Однако, вначале сравниваются сохраненная ранее метка времени максимальная метка времени UNIX и фактическая метка для конкретного файла. Если первая не меньше, чем последняя, то этот файл пропускается, не индексируется. Т.к. он, стало быть, был создан/изменен ранее, он - не новый. А вот если фактическая метка времени файла превышает ранее запасенную метку, вот тогда уже происходит его индексирование. Ибо это означает, что соответствующий файл был изменен (или создан) уже после того, как изменялся/создавался предпоследний (по времени), т.е. наименее недавний файл сайта. Поэтому его содержимое нужно проиндексировать заново.
После чего сохраненная максимальная метка времени обновляется на более свежую. Например, пусть появился еще один файл:
\fileName;timeUNIX:1771967803|index
\123__.php;1771967803|0
\footer.html;1741206927|1
\index.html;1763002869|2
\left.txt;1716806902|3
\right.txt;1716805583|4
\top.txt;1716805672|5
\new.html;1772345678|6
Тогда после индексации его содержимого будет что-то подобное:
\fileName;timeUNIX:1772345678|index
\123__.php;1771967803|0
\footer.html;1741206927|1
\index.html;1763002869|2
\left.txt;1716806902|3
\right.txt;1716805583|4
\top.txt;1716805672|5
\new.html;1772345678|6
Т.е. файлы, содержимое которых ранее уже было проиндексировано и метки которых были учтены при получении максимальной метки, НЕ БУДУТ индексироваться. Потому что их метки времени не превышают максимальную (сохраненную) метку.
Будут лишь просматриваться их имена и сравниваться метки времени. Такой подход привел к существенному росту скорости доиндексации содержимого файлов. Например, просмотр имен всех файлов в количестве несколько тысяч, находящихся в различных каталогах и подкаталогах, а также определение их меток времени, занимает около 1 секунды. Тогда как полное индексирование содержимого этих файлов занимает минут 10-15.
Впрочем, вполне возможно, что я разработал аналог Яндекса какой-нибудь 20-летней давности. Говорят, что раньше даже проводились чемпионаты по поиску. Это когда Яндекс (да и Google) не был достаточно "интеллектуальным". Правда, в отличие от этих поисковых систем, у меня нет запретов на выдачу в поиске тех или иных "запрещенных" результатов. Нет также надуманных и, мягко говоря, странных правил ранжирования, нет манипуляций выдачей. Уж не говоря о том, что, если говорить о Google, вообще нет странного, по полностью необъяснимым причинам, выпадения ряда файлов (вебстраниц) из индекса. Без каких-либо объяснений.
А ведь в таком положении не только я, но и весь интернет, все сайты. ВСЕМ, кто делает сайты, даются расплывчатые и общие слова под видом "рекомендаций". В результате - качество поиска снижается. Судя по моим наблюдениям, с каждым годом.
Ну, а потом я понял, что, как обычно, все гораздо проще; что не стоит искать "темных кошек в темных комнатах", когда их там нет. Не стоит гадать о "высших материях" и технологиях, когда причины - очевидны и банальны. И что причина тут - одна-единственная: в халтуре, а то и беспределе(?) со стороны Google, как поисковой системы. Как только я это понял, так и вообще перестал следить и анализировать, как он там оценивает (или не оценивает) те или иные страницы. В силу ПОЛНОЙ БЕСПОЛЕЗНОСТИ такого анализа. А уж когда в Google резко испортили выдачу результатов поиска (годов так после 2018-2020), что практически невозможно стало найти полезную информацию, если она мало-мальски отличается от общеизвестной, очень популярной и "топовой", так и тем более перестал на него обращать внимание.
А это означает, что хотя бы здесь моя система лучше. Правда, она делает поиск только среди файлов, имеющихся на моем компьютере.
Кстати, а если арендовать хостинг и РАЗРЕШИТЬ людям размещать там свои полезные материалы и давать за это какие-нибудь бонусы (например, разрешать рекламу), тщательно отбирать материалы по их качеству, чтобы люди делали полезные ссылки на другую полезную информацию, так тогда, теоретически, можно вообще обойтись без поисковых систем, тогда можно будет просто зайти К СЕБЕ и найти то, что нужно. А если получить постоянный IP-адрес, так тогда можно будет обойтись и без аренды, храня эти материалы прямо у себя дома на компьютерах. И разрешая к ним доступ всем желающим. Правда, непонятно, кто и как будет делать отбор...
Попутно, добавил также блокировку кнопок, запускающих полное индексирование файлов и их содержимого, а также кнопки удаления каталога с индексными файлами. Чтобы вдруг не ошибиться, не нажать случайно. И сделал кнопку снятия блокировки, в виде красного крестика.
Вот она, версия 2.5.
Версия 2.6
Все-таки, решил я сделать ранжирование, пока - простенькое. Дело в том, что иной раз при поиске выходит множество разных файлов (содержащих искомые слова); желательно, чтобы наиболее актуальные (релевантные) были где-то повыше. Я не знаю, как осуществляется ранжирование в поисковых системах.
Банальная истина: когда что-то скрывается от пользователей, когда вместо правды идут общие слова, а то и прямая ложь, значит, с вероятностью не ниже 100%, речь идет либо о манипуляциях, либо еще и о злоупотреблениях. Все остальное - это уже малозначимые нюансы, на них можно вообще не обращать внимания. Не стоит врать о том, что если пользователи вдруг узнают правду об алгоритмах ранжирования, то они начнут манипулировать и искусственно оптимизировать свои сайты, вебстраницы. Чем, якобы, "испортят" поисковую выдачу.
Например, тот факт, что все религии мира так или иначе лгали своим адептам и другим людям, скрывали от них правду о реальности, привело лишь к обману таких адептов и незаслуженному "уважению" таких адептов в глазах всех остальных. В результате - искаженное мировосприятие и куча связанных с ним проблем. Никакой пользы для людей в целом от этой лжи нет и не было.
Если некое государство (точнее, его власти) делает запутанной систему законодательства, делает невнятной порядок реализации и защиты прав граждан, если в таком государстве можно получить наказание чуть ли не за что угодно (или, напротив, можно НЕ получить наказание даже за тяжкие преступные деяния), то это однозначно, с вероятностью не менее 100%, означает, что власти такого государства являются, как минимум, мошенниками-манипуляторами; а то и тяжкими преступниками - злонамеренными обманщиками.
Если некий член семьи лжет, не договаривает правду другим членам семьи, это однозначно, с вероятностью не менее 100% означает, что он, как минимум, мошенник или тяжкий преступник. Неважно, по работе ли он не договаривает или по иным причинам.
Или взять поисковую систему Google. Она с каждым годом, судя по моей личной практике в Вебмастере, дает все менее конкретные рекомендации, в так называемой "базе знаний" содержится мало конкретики, там, зачастую, одна болтовня. Более того(!), в Google-поиске даже нет никакой службы поддержки. В итоге, закономерный результат: деградация, резкое снижение качества поисковой выдачи Google. Безусловно, руководство Google не может это не понимать.
Я удивляюсь, как же это неочевидно многим другим. Уж XXI век вовсю идет ведь...
Это мы видим и на примере все засекречивавшего советского КГБ (и аналогичных засекречивающих российских спецслужб), это мы видим на примере лживой розовой рекламы о США, это мы видим на примере удушения права на информацию путем блокировки сайтов с соцсетей в России и так далее. Примеров на практике - масса.
Решил сделать по-своему, не знаю, насколько эффективно.
1. Если при поиске задано условие ИЛИ. Т.е. хотим сделать поиск по условию Слово1 ИЛИ Слово2. Видимо, именно тот текст будет релевантнее, который содержит как можно больше этих слов. Т.е. тут можно суммировать частоты их встречаемости.
В рамках данного условия, чем больше слов, объединенных условием ИЛИ, найдено, тем лучше.
2. А вот как быть с условием Слово1 И Слово2 ?
Например, есть два практически одинаковых текста, Текст1 и Текст2. Одинаковых, за исключением того, что в первом тексте Слово1 содержится 1 раз, Слово2 содержится 10 раз. А в другом тексте оба слова встречаются по 2 раза. Какой же из них релевантнее условию И?
Подумал. Потом задал вопрос яндексовой Алисе. Вначале она ответила, что "оба текста одинаково релевантны". Хотя, в вопросе я ясно сформулировал, что они НЕ МОГУТ быть релевантными, т.к. они - разные. Пришлось повторить вопрос, еще сильнее акцентировать на этом внимание. После этого она привела несколько критериев. В том числе, о "перекосе встречаемости слов", о критерии "равномерности встречаемости слов" и прочую малозначимую информацию, как мне видится. Впрочем, ее итоговый вывод оказался правильным.
А какой вывод будет из теоретического анализа, из осознания бытия? Вывод такой: если мы используем условие И, то тогда И Слово1, И Слово2 ОДИНАКОВО важны. Если не указаны веса их важности, разумеется.
А если они ОДИНАКОВО важны, стало быть, нет разницы, какое из них важнее. Стало быть, необходимо искать ОБА слова ОДНОВРЕМЕННО, без учета их конкретики. Это если не учитывать смыслы текстов, не учитывать качество текстов и т.п. Эти слова ЕДИНЫ. И представляют собой ЕДИНОЕ множество, пусть и несвязное. А это очевидно означает, что Текст2 будет более релевантным указанному условию И. Безусловно, в данном случае не имеют значения объемы текстов.
Можно, конечно, создать систему критериев ранжирования, типа условий if... else. Но, мне видится, что ДОЛЖНА существовать единая формула, в виде функции, пусть и неявной. Чисто в силу того, что природа функционирует на основе математических закономерностей. Если речь не идет о душе, поэзии, музыке, чувствах и пр.; ибо там - слегка другие законы.
Простое умножение частот встречаемости здесь не подойдет (впрочем, почему бы и нет...). Пересечение - тоже.
Например, пусть Слово1 встречается в некоем тексте a раз, а Слово2 - b раз. В итоге, для подсчета уровня релевантности результатов поиска при поиске с условием И я придумал такую формулу:
| |a – b| | |
| min(a, b) + | —————— |
| max(a, b) |
Не знаю, насколько она корректная. Впрочем, видимо, она неявно учитывает некоторые предложенные Алисой критерии (о близости частот встречаемости слов, например).
Это - пока самое простое. Возможно, стоит учесть иные, нелинейные функции. Например, степенные или экспоненциальные. Возможно, стоит ввести определенные коэффициенты. Но, для их применения вначале необходимо определить в общем виде определение, критерий релевантности текста. Существуют, конечно, разные критерии. Но, это - лишь гипотезы. Примерно как в сопромате есть разные гипотезы прочности. Возможно, что единый критерий отсутствует.
Ну, а дальше - все просто. Скажем, есть логическое выражение
Слово1 && Слово2 || Слово3
С учетом конкретных частот встречаемости этих слов в том или ином тексте оно приобретет примерно такой вид:
3 && 4 || 6
Если оценить это выражение с помощью обычного eval, то результат будет, видимо, равным 1. А если сделать оценку с учетом вышесказанного, будет иное значение. Его и можно обозначить в качестве уровня релевантности и на его основе ранжировать конкретный текст.
Правда, вот я (пока) обо что споткнулся. Если суммировать количества вхождений каждого слова для конкретного файла (и, соответственно, для конкретного индекса из files.txt), то как быть при ОЧЕРЕДНОМ индексировании? Количества вхождений для индексов-то просуммируются заново... В том числе и при дополнительном индексировании, если соответствующий файл будет изменен. Раньше-то проще было: индекс мог либо быть, либо отсутствовать. А если складывать числа их вхождений...
Можно, конечно, хранить информацию о состоявшемся факте индексирования конкретного файла (чтобы больше его не индексировать, до особой необходимости). А если их будет много, скажем, миллион? Много места займет такая информация. Несколько десятков мегабайт. Как-то не очень это все...
В общем, решил я вначале делать подсчет числа тех или иных слов (метафонов) в индексируемом файле. Потом в индексных файлах вначале удаляю индекс и число слов, а потом вставляю заново.
Кстати, любопытно, если делать это при помощи регулярного выражения, применительно ко всему содержимому индексного файла, то получится в несколько раз быстрее, чем когда создавать из содержимого файла массив (построчно) и искать необходимый индекс в каждой из строк.
Внезапно я вдруг обнаружил, что при нечетком поиске может появляться масса лишних искомых слов. Например, ищем слово "ногу". Согласно словарю, если убрать окончание "у", существует слово "ног", затем добавляем окончание "а" и получаем "нога". Также будут добавлены слова "ногой", "ногами", "ног", "ногах".
Однако, существует слово "могу". Если убрать окончание "гу", а затем добавить "чь", получится слово "мочь". Аналогично из слова "ногу" получится слово "ночь". Поэтому слово "ночь" также будет включено в состав искомых слов (как будто это - "ногу", только с другим окончанием). Ну, а уже исходя из этого добавятся слова, типа "ночью", "ночами", "ночей" и т.д. Т.е. диапазон поисковых слов неоправданно расширится. Как с этим бороться, я пока не знаю. Как устроен алгоритм использования словаря (например, в браузере) - тоже не знаю. Да и неохота его искать и разбираться.
Что придумал. Вначале убираю как можно более короткие окончания. Затем добавляю окончание, которое создает начальную форму слова. И затем ее проверяю по словарю, присутствует она там или нет. Если да, то считываю оттуда суффикс (если он есть). И потом, в соответствии с правилами, заданного для конкретных суффиксов, получаю всевозможные формы слова (т.е. с разными окончаниями).
Например, если взять слово "ногу", то начальная форма определится как "нога", т.к. достаточно убрать только одну букву "у" (окончание) и затем добавить окончание "а". Это слово присутствует в словаре. Тогда буквы "гу" уже убираться не будут. Так, вроде бы, проблема решилась.
Однако, потом обнаружилось, что некоторые слова индексируются с неправильными (словарными) суффиксами. Оказалось, проблема - в излишне короткой метафонизации. В результате, получались одинаковые метафоны у слов "страна" и "стираный". А суффиксы-то у начальных форм этих слов - разные. В итоге, слово "страна" склонялось с чужим суффиксом и получались неправильные словоформы.
Пришлось увеличить заданное число символов при метафонизации. Ранее я использовал 5 символов, решил использовать 7. Проблема решилась, но возникла другая проблема: резко увеличился объем каталога metaphones. Ведь чем больше символов содержат метафоны, тем больше требуется каталогов для хранения индексных файлов. В итоге, вместо 30 МБ этот каталог стал занимать почти 400 МБ. При том, что число каталогов там составило 100 с лишним тысяч.
Сами-то индексные файлы имеют очень малый объем, всего лишь пару мегабайт суммарно. Однако, минимальный объем, занимаемый каждым таким файлом на жестком диске, даже если он содержит 1 байт, составляет 4096 байт (в Windows 7, по крайней мере). Из-за этого, так как индексные файлы имеют, как правило, очень небольшой объем, и получился достаточно высокий общий объем каталогов.
По идее, это не столь актуально, если число индексируемых файлов (сайта) достаточно высоко, больше 10...30 тысяч. Тогда объем этого каталога увеличится несильно и он будет существенно ниже, чем объем самих индексируемых файлов. Тем не менее, желательно бы, конечно, как-то оптимизировать.
Если использовать некую разметку внутри индексных файлов (подозреваю, что так сделаны многие базы данных), то их объем существенно возрастет. И, вполне возможно, даже превысит эти 400 МБ.
Если сохранять не 2 последних символа метафона, а 3, то это приведет к резкому росту объема индексных файлов. Т.е. если вместо условного
lk|;677;11;30;256;
использовать что-то вроде
lkp|;677;11;30;256;
В первом случае, с учетом 26 символов английского алфавита, получится не более 26*26 = 676 строк в каждом индексном файле. Во втором случае - не более 26*26*26 = 17576 строк. При том, что каждая строка может иметь достаточно много символов (индексов). Поиск в таких файлах будет занимать много времени. Но, зато будет решена проблема наличия слишком малых файлов. И каталогов будет гораздо меньше.
Конечно, если объем индексного файла будет слишком высоким, то можно брать из него наиболее большие строки (близкие к 4096 символов или выше) и сохранять их в отдельных файлах. Т.е. помимо файлов 1.txt будут также файлы вида lkp.txt. Так скорость поиска даже увеличится. Правда, увеличится и число индексных файлов.
Или, как вариант, использовать 2 или 3 последних символа в зависимости от общего объема индексируемого контента файлов сайта. Если файлов немного, то использовать 3 (а то и 4?) символа. А, точнее, тогда лучше вообще никакую систему не делать, а использовать обычные базы данных, например, MySQL какую-нибудь. Это будет существенно проще, да и экономичнее. Не потребуется занимать на жестком диске целые 360 МБ (в виде сотни тысяч с лишним файлов и каталогов) только ради индексации словаря, даже если контент сайта индексироваться не будет вообще. А вот когда файлов побольше, когда их общий объем (текстового содержимого) составит сотни мегабайт, вот тогда вместо баз данных стоит, вероятно, использовать мою систему. Если взять среднюю статью-файл, занимающую 10 кБ, то 300 МБ займут 30.000 файлов. В принципе, это - объем среднего, не слишком большого сайта. Начинающий портал.
Если их - много, то 2 символа или, быть может, даже 1. Последнее - это если делать большую поисковую систему типа Яндекс, Bing или Google. Там, единственное, что потребуется добавить - это очереди на обработку запросов на индексирование.
Можно также использовать архивирование zip, благо в РНР есть такая возможность. Архив будет занимать раз в 20 меньше места (т.е. примерно столько, сколько байт фактически занимают файлы 1.txt). Впрочем, с zip - проблема: т.к. каталогов и файлов очень много (сотня с лишним тысяч), zip-архив не создается. Если же использовать rar, то возможности РНР там ограничены, т.к. это - лицензионная программа. Получается, если только делать свой архиватор. Но, в нем нет смысла, проще уж тогда изначально упаковать индексы в меньшее количество файлов, т.е. компактнее.
Версия 2.7
В этой версии я, в первую очередь, решил снизить объем индексных файлов путем представления индексов не в 10-чном, а в N-ричном виде. Для алфавита использовал строку:
0123456789abcdefghijklmnopqrsstuvwxyzABCDEFGHIJKLMNOPQRSSTUVWXYZ
В ней 64 символа, т.е. получается 64-ричный алфавит. Через него я выразил десятичные индексы. В итоге, например, для четырехзначных 10-чных индексов получились двузначные 64-чные. Т.е. экономия дисковой памяти - примерно в 2 раза (точнее, немного ниже). Например, вместо 2737 получилось что-то вроде 3f. Кроме того, если какое-либо слово (метафон) содержится в конкретном файле только 1 раз, то вместо 3f*1 записывал просто 3f. Тоже неплохая экономия, т.к. слов, содержащихся по одному разу в тех или иных файлах, немало.
Также можно в алфавит добавить еще и русские символы (кириллицу) в нижнем и верхнем регистрах. Так добавится еще 64 символа (всего будет алфавит из 128 символов). Однако, для экономичности придется хранить все индексные файлы в однобайтовой кодировке, например, в Windows-1251. А не в UTF-8 (т.к. там кириллический символ занимает 2 байта). Впрочем, даже и в случае двух байт все равно будет дополнительная экономия.
Также хотел расширить алфавит за счет непечатаемых ASCII-символов, типа NUL, SOH и пр. Они, по идее, часто применяются в файлах баз данных. Но, как оказалось, при их наличии невозможно корректно определить кодировку файла, функция mb_check_encoding перестает работать. Можно, конечно, вначале удалять из содержимого файла все эти символы, затем определить его кодировку и после этого использовать изначальное его содержимое с этой уже известной кодировкой.
Это - пока на раздумьи. Это актуально, когда индексируемых файлов сайта будет от 100 тысяч.
Версия 2.8
Предыдущие версии создают большое число каталогов (и файлов в них с именами 1.txt) для хранения индексов. Т.к. файлы эти, как правило, маленькие, но на них выделяется не менее 4 кБ (4096 байт) дискового пространства.
В итоге, общий объем индексированной информации занимает 300-400 МБ на диске.
Поэтому я сделал так. Если объем индексного файла (называю его отдельным индексным файлом) менее, чем заданная величина, например, 2 кБ, то такой файл не создается, вместо этого индексы хранятся в общем индексном файле. Этот файл - общий для первых букв метафонов. Например, для таких метафонов, как aslk, abdfgf, anrdfs будет использован один общий файл, имеющий в имени букву a.
А вот когда для конкретного метафона объем индексов станет выше, тогда уже создается отдельный индексный файл, вместе с соответствующей структурой каталогов (при этом индексы для него полностью удаляются из общего индексного файла). Например, файл /a/b/s/n/1.txt будет содержать индексы для таких метафонов, как absnga, absnag, absntm и т.д. (как и было в предыдущих версиях).
В результате, объем индексированной информации на жестком диске снизился раз в 40, почти на два порядка. И, что любопытно, время, затрачиваемое на поиск, практически не изменилось (не увеличилось). Ранее я опасался, что если индексный файл будет занимать от 400-500 кБ, то время поиска возрастет. Оказалось - нет, даже при размере около 1 МБ поиск индексов в нем осуществляется довольно быстро.
Вполне возможно, даже быстрее получится в итоге. Потому как, такое впечатление, гораздо быстрее открыть ОДИН, пусть и сравнительно большой файл с индексами и там искать. Чем открывать множество мелких файлов, пусть и при известных путях к ним. Потому что для открытия КАЖДОГО файла требуется подождать, пока жесткий диск повернется нужным образом и пока этот файл не будет найден и прочитан. Кроме того, если файл содержит всего пару сотен байт, а буфер контроллера жесткого диска имеет емкость от сотен килобайт (а для современных дисков - многие десятки мегабайт), то НЕТ смысла дергать диск каждый раз при чтении каждого из множества мелких файлов. Это будет неэффективно.
Тогда как один большой файл читается сразу и, более того, еще и поблочно.
Разницы не будет, разве что, для SSD-дисков.
Похоже, данный алгоритм одинаково хорош и для малых сайтов, и для больших. Для малых будут образовываться только общие индексные файлы. А для больших - будут и отдельные. При этом они будут создаваться лишь при реальной потребности (при достаточно большом числе индексов для соответствующих метафонов), а не ради десятка-сотни байт индексов. А вот предыдущие версии алгоритмов были бы лучше для совсем старых компьютеров.
Вот она, версия 2.8.
С уважением, Салимоненко Д.А.