Пролог
Добрый день, уважаемые читатели. В данной статье я расскажу о том, как распарсить число, записанное прописью на русском языке.
Умным данный парсер делает возможность извлечения чисел из текста с ошибками, допущенными в результате некорректного ввода или в результате оптического распознавания текста из изображения (OCR).
Для ленивых:
Ссылка на проект github: ссылка.
От алгоритма до результата
В данном разделе будут описаны использованные алгоритмы. Осторожно, много букв!
Постановка задачи
По работе мне требуется распознать текст из печатного документа, сфотографированного камерой смартфона/планшета. Из-за соглашения о неразглашении информации я не могу привести пример фотографии, но суть в том, что в документе имеется таблица, в которой записаны некие показатели числом и прописью, и эти данные необходимо прочитать. Парсинг текста прописью необходим как дополнительный инструмент валидации, чтобы убедиться, что число распознано верно. Но, как известно, OCR не гарантирует точного распознавания текста. Например, число двадцать, записанное прописью, может быть распознано как «двадпать» или даже как «двапать». Нужно это учесть и извлечь максимальное количество информации, оценив величину возможной ошибки.
Примечание. Для распознавания текста я использую tesseract 4. Для .NET нет готового NuGet-пакета четвертой версии, поэтому я создал такой из ветки основного проекта, может кому пригодится: Genesis.Tesseract4.
Базовый алгоритм парсинга числа
Начнем с простого, а именно с алгоритма распознавания текста, записанного прописью, пока без ошибок. Если вам интересен именно умный парсинг, данный раздел можно пропустить.
Я не особо силен в гуглении, поэтому сходу не нашел готовый алгоритм для решения этой задачи. Впрочем, это даже к лучшему, т.к. алгоритм, придуманный самим, дает больше простора для кодинга. Да и сама задача оказалось интересной.
Итак, возьмем небольшое число, например «сто двадцать три». Оно состоит из трех слов (токенов), каждому из которых соответствует число, все эти числа суммируются:
"сто двадцать три" = сто + двадцать + три = 100 + 20 + 3 = 123
Пока все просто, но копнем глубже, например рассмотрим число «двести двенадцать тысяч сто пять».
"двести двенадцать тысяч сто пять" = (двести + двенадцать) ? тысяч + (сто + пять) = 212 * 1.000 + 105 = 212.105.
Как видим, когда в числе присутствуют тысячи (а также миллионы и прочие степени тысячи), число делится на части, состоящие из локального маленького числа, в примере выше – 212, и множителя (1000). Таких фрагментов может быть несколько, но все они идут по убыванию множителя, например, за тысячей не может последовать миллион или еще одна тысяча. Это верно также и для частей маленького числа, так за сотнями не могут последовать сотни, а за десятками — десятки, поэтому запись «сто пятьсот» неверна. Характеристику, относящую два токена к одному типу, назовем уровнем, например, токены «сто» и «триста» имеют один уровень, и он больше, чем у токена «пятьдесят».
Из этих рассуждений и рождается идея алгоритма. Давайте выпишем все возможные токены (образцы), каждому из которых поставим в соответствие число, а также два параметра – уровень и признак множителя.
Токен | Число | Уровень | Множитель? |
---|---|---|---|
ноль |
0 |
1 |
нет |
один/одна |
1 |
1 |
нет |
два/две |
2 |
1 |
нет |
… |
… |
1 |
нет |
девятнадцать |
19 |
1 |
нет |
двадцать |
20 |
2 |
нет |
… |
… |
2 |
нет |
девяносто |
90 |
2 |
нет |
сто |
100 |
3 |
нет |
… |
… |
3 |
нет |
девятьсот |
900 |
3 |
нет |
тысяча/тысячи/тысяч |
1.000 |
4 |
да |
миллион/миллиона/миллионов |
1.000.000 |
5 |
да |
… |
… |
… |
да |
квадриллион/квадриллиона/квадриллионов |
1.000.000.000.000.000 |
8 |
да |
На самом деле, в эту таблицу можно добавить и любые другие токены, в том числе для иностранных языков, только не забывайте, что в некоторых странах используется длинная, а не короткая система наименования чисел.
Теперь перейдем к парсингу. Заведем четыре величины:
- Глобальный уровень (globalLevel). Указывает, какой уровень был у последнего множителя. Изначально не определен и необходим для контроля. Если мы встретим токен-множитель, у которого уровень больше или равен глобальному, то это ошибка.
- Глобальное значение (globalValue). Общий сумматор, куда складывается результат результат перемножения локального числа и множителя.
- Локальный уровень (localLevel). Указывает, какой уровень был у последнего токена. Изначально не определен, работает аналогично глобальному уровню, но сбрасывается после обнаружения множителя.
- Локальное значение (localValue). Сумматор токенов, не являющихся множителями, т.е. чисел до 999.
Алгоритм выглядит следующим образом:
- Разбиваем строку на токены с помощью регулярки "\s+".
- Берем очередной токен, получаем информацию о нем из образца.
- Если это множитель:
- Если задан глобальный уровень, то убеждаемся, что он больше или равен уровню токена. Если нет – это ошибка, число некорректно.
- Устанавливаем глобальный уровень на уровень текущего токена.
- Умножаем величину токена на локальное значение и добавляем результат к глобальному значению.
- Очищаем локальное значение и уровень.
- Если это не множитель:
- Если задан локальный уровень, то убеждаемся, что он больше или равен уровню токена. Если нет – это ошибка, число некорректно.
- Устанавливаем локальный уровень на уровень текущего токена.
- Прибавляем к локальному значению величину токена.
- Возвращаем результат как сумму глобального и локального значений.
Пример работы для числа «два миллиона двести двенадцать тысяч сто восемьдесят пять».
Токен |
globalLevel |
globalValue |
localLevel |
localValuе |
---|---|---|---|---|
– |
– |
– |
– |
|
два |
– |
– |
1 |
2 |
миллиона |
5 |
2.000.000 |
– |
– |
двести |
5 |
2.000.000 |
3 |
200 |
двенадцать |
5 |
2.000.000 |
1 |
212 |
тысяч |
4 |
2.212.000 |
– |
– |
сто |
4 |
2.212.000 |
3 |
100 |
восемьдесят |
4 |
2.212.000 |
2 |
180 |
пять |
4 |
2.212.000 |
1 |
185 |
Результатом будет 2.212.185.
Умный парсинг
Данный алгоритм можно использовать для реализации других сопоставлений, а не только для парсинга чисел, по этой причине я постараюсь описать его как можно подробнее.
С парсингом корректно записанного числа разобрались. Теперь давайте подумаем, какие ошибки могут быть при неверной записи числа, полученного в результате OCR. Другие варианты я не рассматриваю, но вы можете модифицировать алгоритм под конкретную задачу.
Я выделил три вида ошибок, с которыми столкнулся в процессе работы:
- Замена символов на другие со схожим начертанием. Например, буква «ц» почему-то заменяется на «п», а «н» на «и» и наоборот. При использовании третьей версии tesseract возможна замена буквы «о» на ноль. Эти ошибки, навскидку, самые распространенные, и требуют тюнинга под конкретную библиотеку распознавания. Так так принципы работы tesseract версий 3 и 4 имеют кардинальные различия, поэтому и ошибки там будут разными.
- Слияние токенов. Слова могут сливаться воедино (обратного пока не встречал). В комбинации с первой ошибкой порождает демонические фразы типа «двапатьодин». Попробуем раздраконить и таких монстров.
- Шум – левые символы и фразы в тексте. К сожалению, здесь мало что можно сделать на данный момент, но перспектива есть при сборе достаточно весомой статистики.
При этом сам алгоритм разбора, описанный выше, почти не меняется, главное различие в разбиении строки на токены.
Но начнем со сбора небольшой статистики использования букв в токенах. Из 33 букв русского языка при написании неотрицательных целых чисел используются только 20, назовем их хорошими буквами:
авдеиклмнопрстцчшыья
Остальные 13, соответственно, назовем плохими буквами. Максимальный размер токена при этом составляет 12 символов (13 при счете до квадриллионов). Подстроки длиной больше этой величины нужно разбивать.
Для сопоставления строк и токенов я решил использовать алгоритм Вагнера-Фишера, хотя и назвал его именем Левенштейна в коде. Редакционное предписание мне не нужно, поэтому я реализовал экономную по памяти версию алгоритма. Должен признаться, что реализация этого алгоритма оказалась более сложной задачей, чем сам парсер.
Небольшой ликбез: расстояние Левенштейна – частный случай алгоритма Вагнера-Фишера, когда стоимость вставки, удаления и замены символов статичны. В нашей задаче это не так. Очевидно, что если в подстроке мы встречаем плохую букву, то ее нужно заменить на хорошую, а вот заменять хорошую на плохую крайне нежелательно. Вообще говоря, нельзя, но ситуация зависит от конкретной задачи.
Для описания стоимости вставки, удаления и замены символов я создал такую таблицу: ссылка на таблицу с весами. Пока она заполнена методом трех П (пол, палец, потолок), но если заполнить ее данными на основе статистики OCR, то можно значительно улучшить качество распознавания чисел. В коде библиотеки присутствует файл ресурсов NumeralLevenshteinData.txt, в который можно вставить данные из подобной таблицы с помощью Ctrl+A, Ctrl+C и Ctrl+V.
Если в тексте встречается нетабличный символ, например, ноль, то стоимость его вставки приравнивается к максимальной величине из таблицы, а стоимость удаления и замены – к минимальной, таким образом алгоритм охотнее заменит ноль на букву «о», а если вы используете третью версию tesseract, то имеет смысл добавить ноль в таблицу и прописать минимальную цену для замены его на букву «о».
Итак, данные для алгоритма Вагнера-Фишера мы подготовили, давайте внесем изменения в алгоритм разбиения строки на токены. Для этого каждый токен мы подвергнем дополнительному анализу, но перед этим расширим информацию о токене следующими характеристиками:
- Уровень ошибки. Вещественное число от 0 (ошибки нет) до 1 (токен некорректен), означающее, насколько хорошо токен был сопоставлен с образцом.
- Признак использования токена. При разборе строки с вкраплениями мусора, часть токенов будет отброшена, для них данный признак выставляться не будет. При этом итоговая величина ошибки будет считаться как среднее арифметическое от ошибок использованных токенов.
Алгоритм анализа токенов:
- Пытаемся найти токен в таблице как есть. Если находим – все хорошо, возвращаем его.
- Если нет, то составляем список возможных вариантов:
Пытаемся сопоставить токен с образцом с помощью алгоритма Вагнера-Фишера. Данный вариант состоит из одного токена (сопоставленного образца) и его ошибка равна лучшему расстоянию, поделенному на длину образца.
Пример: токен «нуль» сопоставляется с образцом «ноль», при этом расстояние равно 0.5, т.к. стоимость замены плохой буквы «у» на хорошую «о» равна 0.5. Общая ошибка для данного токена будет 0.5 / 4 = 0.125.
Если подстрока достаточно большая (у меня это 6 символов), пытаемся поделить ее на две части минимум по 3 символа в каждой. Для строки в 6 символов будет единственный вариант деления: 3+3 символа. Для строки в 7 символов – уже два варианта, 3+4 и 4+3, и т.д. Для каждого из вариантов вызываем рекурсивно эту же функцию анализа токенов, заносим полученные варианты в список.
Чтобы не умирать в рекурсии, определяем максимальный уровень проваливания. Кроме того, варианты полученные в результате деления искусственно ухудшаем на некую величину (опция, по умолчанию 0.1), чтобы вариант прямого сопоставления был более ценным. Эту операцию пришлось добавить, т.к. подстроки типа «двапать» успешно делились на токены «два» и «пять», а не приводились к «двадцать». Увы, таковы особенности русского языка.
Пример: токен «двапать» имеет прямое сопоставление с образцом «двадцать», ошибка 0.25. Кроме того, лучшим вариантом деления является «два» + «пять» стоимостью 0.25 (замена «а» на «я»), ухудшенная искусственно до 0.35, в результате чего предпочтение отдается токену «двадцать».
- После составления всех вариантов выбираем лучший по минимальной сумме ошибок участвующих в нем токенов. Результат возвращаем.
Кроме того, в основной алгоритм генерации числа вводится проверка токенов, чтобы их ошибка не превышала некую величину (опция, по умолчанию 0.67). С помощью этого мы отсеиваем потенциальный мусор, хотя и не очень успешно.
Алгоритм в двух словах для тех, кому было лень читать текст выше
Входящую строку, представляющую число прописью мы разбиваем на подстроки с помощью регулярки \s+, затем каждую из подстрок пытаемся сопоставить с токенами-образцами или разбить на более мелкие подстроки, выбирая при этом лучшие результаты. В итоге получаем набор токенов, по которым генерируем число, а величину ошибки принимаем за среднее арифметическое ошибок среди токенов, использованных при генерации.
Заточка алгоритма под конкретную задачу
В моей задаче числа неотрицательные и относительно небольшие, поэтому я исключу ненужные токены от «миллиона» и выше. Для теста, уважаемые читатели, я, напротив, добавил дополнительные токены-жаргонизмы, что позволило парсить строки типа «пять кусков», «косарь двести» и даже «три стольника и два червонца». Забавно, но это даже не потребовало изменений в алгоритме.
Дальнейшее улучшение
У существующего алгоритма есть и недоработки:
- Контроль падежей. Строки «две тысячи» и «два тысячей» будут с нулевой ошибкой распознаны как 2000. В моей задаче контроль падежей не требуется, он даже вреден, но если вам нужна такая функция, это решается введением дополнительного флага в токен, отвечающего за падеж следующего токена.
- Отрицательные числа. Вводится дополнительный токен «минус» с особой обработкой. Ничего сложного, но не забудьте, что буква «у» является плохой, и не встречается в числительных, нужно будет изменить ее весовые характеристики или надеятся, что она не изменится в процессе OCR.
- Дробные числа. Решается заменой типа long на double и введением токенов «десятых», «сотых» и т.п… Не забудьте пересмотреть весы букв.
- Распознавание чисел, введенных пользователями. Т.к. при вводе текста вручную мы чаще всего допускаем ошибки, связанные с переТСановкой сиВМолов, следует добавить эту операцию в алгоритм Вагнера-Фишера.
- Поддержка других языков. Вводим новые токены, расширяем таблицу весов.
- Обработка мусора. В некоторых документах на данные налезает печать, качество изображением может быть плохим, ячейка может быть банально пустой. В этом случае в строку попадает мусор, который нужно как-то чистить. Лучшее, что я могу предложить на данный момент – производить предварительную обработку документа перед OCR. Мне очень сильно помогло удаление линий таблицы и заливка их цветом, близким к цвету свободного пространства ячейки. Это не решило все проблемы, но улучшило качество распознавания текста с документов, где таблица имела искривления из-за помятости документа или криворукого фотографа. В идеале стоит доворачивать саму ячейку и распознавать ее отдельно, если у вас, конечно, вообще имеется таблица.
Так что в итоге?
В проекте есть пример консольного приложения, бегущего по файлу samples.txt с примерами для парсера. Вот скриншот результатов:
Оценивать результат поручаю вам, но как по мне, он неплох. Величина ошибки для реальных примеров распознавания не превышает 0.25, хотя я еще не прогонял весь набор имеющихся документов, наверное, не все там будет так гладко.
Что касается последнего раздела, мне всегда было интересно, сколько же это – «дофига». Также программа дала вполне себе адекватный ответ, сколько нужно принимать на посошок (я не употребляю, но все же) и даже точно определила значение древнерусского слова «тьма». И да, в вывод не вошла еще одна мера, которую воспитание добавить не позволило, но программа считает, что она равна тысяче =)
Пару слов о библиотеке
Изначально в мои планы не входило создание библиотеки, оформить ее я решил исключительно для хабра. Код постарался привести в порядок, но если будете использовать, делайте форк или копию, т.к. скорее всего вам не потребуются жаргонизмы и прочие токены, включенные в примеры.
Сама библиотека написана под .NET Standart 2.0 и C# 7.x, а алгоритмы легко переводятся на другие языки.
На случай возможного расширения библиотеки добавлю состав важных составляющих парсера чисел прописью (пространство имен Genesis.CV.NumberUtils):
- RussianNumber.cs – непосредственно парсер
- RussianNumber.Data.cs – файл с описанием токенов
- RussianNumber.ToString.cs – конвертер числа в текст прописью
- RussianNumberParserOptions.cs – опции парсера
- NumeralLevenshtein.cs – реализация алгоритма Вагнера-Фишера
- NumeralLevenshteinData.txt – ресурс, данные весов букв
Использование:
- RussianNumber.ToString(value) – преобразование числа в текст
- RussianNumber.Parse(value, [options]) – преобразование текста в число
Заключение
Очень надеюсь, что статья не показалась вам скучной даже несмотря на обилие текста. В последнее время у меня появилось множество тем, связанных с компьютерным зрением, о которых есть что рассказать, поэтому хотелось бы узнать мнение насчет такого формата статей. Что стоит добавить или, наоборот, удалить? Что больше интересно вам, читатели, сами алгоритмы или фрагменты кода?
Понравилась статья? Посмотрите другие:
Комментарии (56)
APXEOLOG
27.05.2019 12:52Что касается последнего раздела, мне всегда было интересно, сколько же это – «дофига»
То есть алгоритм в принципе любую фразу/слово переведет в число, даже нечисловую?
Doomer3D Автор
27.05.2019 13:10Разумеется. Если в слове «жук» сделать две ошибки, то получится «ежик», и хотя это совершенно разные слова, с точки зрения программы они отличаются лишь наполовину.
Алгоритм помимо значения возвращает величину ошибки в пределах от 0 до 1. По ней можно сделать вывод, считать ли полученное число нужным Вам результатом или нет.
vyo
27.05.2019 13:52https://ru.wikipedia.org/wiki/Тьма:
Тьма — число в старинном русском счёте, равное десяти тысячам в малом счёте либо миллиону (тьма великая).
Верно распознал, однако!
barbanel
27.05.2019 14:06Класс!
Что больше интересно вам, читатели, сами алгоритмы или фрагменты кода?
Алгоритмы!
Заголовок спойлераGermanNumber.Parse(«VierUndFunfzigTausendDreiHundertAchtUndZwanzig»);Taraflex
27.05.2019 14:09может быть распознано как «двадпать» или даже как «двапать»
Не знаю как в четверке, но в трешке можно было задать белый список слов
+ какое-нибудь огромное пенальти для слов не из словаря (language_model_penalty_non_dict_word)
+ белый список букв (tessedit_char_whitelist)Doomer3D Автор
27.05.2019 14:22Сначала я использовал третью версию tesseract. Белый список символов позволил хорошо распознавать сами числа, но не текст прописью. Для некачественных изображений ситуация была ужасная. Кроме того, пенальти для слов не из словаря, – нерекомендуемое средство, оно не только не улучшает разбор, а напротив, зачастую ухудшает его.
Переход на четвертую версию позволил достаточно хорошо распознавать как числа, так и текст. Но в четвертой версии, по крайней мере пока, не поддерживается белый/черный список. Единственный вариант, как можно повлиять на разбор – переобучить tesseract, а это достаточно сложный процесс.
В целом же четверка дает значительно более качественный разбор, чем тройка при меньшем времени обработки, что для моей задачи критично.
Aracon
27.05.2019 15:08Спасибо, интересный алгоритм, сразу несколько интересных решений применено.
В моменте, где идёт деление токена на части ввиду возможного слияния и есть проблема «двапать», показалось возможным решением сохранять варианты до последующего парсинга: «два пять» отсеклось бы как некорректный вариант. С другой стороны, давать некоторый штраф разделенным токенам по сравнению с исходными в любом случае нужно, и если на практике этого хватает, то и нет смысла усложнять.
Alex_Hitech
27.05.2019 21:20Очень интересный код, спасибо! Сразу начал обдумывать возможное портирование на С :)
Pyatnitsev
27.05.2019 21:54А что будет на «пять мультов»?
Doomer3D Автор
27.05.2019 22:00Будет 5.000.000. Хотя токена «мульт» нет существует, слово сопоставляется с токеном «миллион» функцией Вагнера-Фишера лучше, чем с другими токенами.
MaxVetrov
27.05.2019 22:02У меня другой вопрос возник: А что если «вотьлум ьтяп»? :)
Doomer3D Автор
27.05.2019 22:08Ну это из разряда фантастики. «вотьлум» сопоставилось с 800 с ошибкой 0.611, а «ьтяп» вообще ни с чем не сопоставилось. Итого 800.
MaxVetrov
27.05.2019 22:53Хорошо. А если «осемнадцатьмгновенийвесны»? 18 или 17?
PS. Спасибо за статью!Doomer3D Автор
27.05.2019 23:11На самом деле, «осемнадцать» – интересный пример. Казалось бы, возможно равное сопоставление как с «восемнадцать», так и с «семнадцать», но добавление буквы «в» стоит немного дешевле, чем удаление «о», т.к. длина токена «восемнадцать» больше на 2 символа, получаем 1 / 12 (0.083) против 1 / 10 (0.100). Поэтому однозначный ответ – 18 с величиной ошибки 0.083.
MaxVetrov
27.05.2019 23:26Понятно.) Ну, а что он скажет на «всемнадцатьчасов»? (в 17 часов)
Полагаю, из вышеуказанного, будет 18.MaxVetrov
28.05.2019 00:27Еще можно посмотреть как поведет себя система на:
«дебять», «дерять», «деоять» — 10 или 9?
«чтри» — 3 или 4?kuber
28.05.2019 08:32Почему вы не хотите скачать проект и экспериментировать с ним сколько вам угодно? Ссылка на проект есть в статье.
MaxVetrov
28.05.2019 10:41Да что-то не компилится на Linux сразу.
Doomer3D Автор
28.05.2019 14:26Консольное приложение нацелено на .NET Core 2.1, сама библиотека для .NET Standart 2.0. Если не запускается под линухой, значит там устаревшая версия .NET Core.
Как вариант, можно перенацелить проект на более старую версию коры, должно работать.
masyaman
28.05.2019 23:27Как мне кажется, для таких случаев стоит использовать какой-то параметр уверености. Например, рассматривать 2 наиболее вероятных варианта. И не парсить слово, если у них ошибки достаточно близки.
Но тут, конечно, от задачи зависит. Где-то может быть нужно распарсить любой ценой, а где-то неправильный или неоднозначный парсинг недопустим.
8street
28.05.2019 09:30Надо закоммитить новые токены
1—4 Few (мало)
5—9 Several (несколько)
10—19 Pack (стая)
20—49 Lots (скопище)
50—99 Horde (орда)
100—249 Throng (толпа)
250—499 Swarm (туча)
500—999 Zounds (тьма)
более 1000 Legion (легион)Cerberuser
28.05.2019 10:33Для этого надо научить выдавать диапазон, а не только число. Кстати, интересная мысль...
Fen1kz
28.05.2019 11:27+1Это только когда астрологи объявят неделю токенов — тогда можно увеличить количество токенов и токенов-берсерков вдвое
McKinseyBA
28.05.2019 11:47ИМХО — в разделе экспертного мнения не хватает «до… я». Doomer3D можно Вас попросить прогнать через модель? Скриншот не обязателен, но результат очень интересен…
Cerberuser
28.05.2019 12:40И да, в вывод не вошла еще одна мера, которую воспитание добавить не позволило, но программа считает, что она равна тысяче =)
Я так понял, что это оно самое и есть.
(upd: автор опередил)
ganqqwerty
28.05.2019 12:24А как насчет склонений и вариаций ошибок в них? Вещи вроде «восемьюстами пятьюдесятью пятью дисками», «к семистам тридцати восьми детям». Думаю, что в таких числах многие носители допускают ошибки, типа «к семьсот тридцати восьми детям». Как забарывали?
siziyman
28.05.2019 12:37+1С иностранными языками как направлением развития мысль прикольная, но тут надо быть _очень_ осторожным: в разных языках сильно разные подходы к формированию чисел. В финском например правильным написанием многозначных чисел является слитное (стодвадцатьтритысячичетырестапятьдесятшесть), вне зависимости от их длины — надо заметно усложнять токенизацию, в немецком — тоже, кажется, слитно, но что важнее, там не очень «прямой» порядок числительных — в каждом классе (сотни-десятки единицы {тысяч, миллионов, <просто>}) там порядок сотни-единицы-десятки, если не говорить о словах, подобных английским eleven, twelve — 11 и 12. А что-то вроде 121 на немецком будет hunderteinundzwanzig — сто-один-и-двадцать, если пытаться воспроизвести это на русском.
Doomer3D Автор
28.05.2019 12:46Это тема для второй части.
Один из пользователей пообещал расписать правила для немецкого, для английского я и сам сделаю.
Если у кого есть желание, можете прислать мне на почту правила для интересующего вас языка (другими языкам я не владею), а я запилю конвертер для них.siziyman
28.05.2019 14:33Я в общем и целом описал то, что сам знаю (финский покрывается этим описанием примерно полностью — всё как у нас примерно, но слитно) — практического интереса у меня нет совсем, посмотреть на результат/почитать статью я бы с радостью. Плюсиком бы наградил что за эту, что за грядущую, но кармы нет. :)
MaxVetrov
28.05.2019 16:40В принципе, не обязательно знание языка для чисел, в инете информация есть. Вот для испанского, например, если интересно.
MaxKom
класс. блеск. восторг.
сорок сороков плюсов.
UncleJey
Ну наконец-то мы узнаем где это тридевятое царство тридесятое государство.
И насколько далёки тридевять земель.
MaxKom
т.е. парсер ещё и привязку по местности выдаёт?
UncleJey
точнейшие координаты в пространственно-временном контиуунуме с привязкой к конкретной спирали вероятностной матрицы