Ввод: "tableapplechairtablecupboard..."
много слов
Какой был бы эффективный алгоритм, чтобы разбить такой текст на список слов и получить:
Вывод: ["table", "apple", "chair", "table", ["cupboard", ["cup", "board"]], ...]
Первое, что приходит в голову, - это перебрать все возможные слова (начиная с первой буквы) и найти как можно более длинное слово, продолжая с position=word_position+len(word)
PS
У нас есть список всех возможных слов.
Слово «шкаф» может быть «чашкой» и «доской», выберите самое длинное.
Язык: питон, но главное - сам алгоритм.
['able', 'air', 'apple', 'boa', 'boar', 'board', 'chair', 'cup', 'cupboard', 'ha', 'hair', 'lea', 'leap', 'oar', 'tab', 'table', 'up']
Ответы:
Наивный алгоритм не даст хороших результатов при применении к реальным данным. Вот 20-строчный алгоритм, который использует относительную частоту слов для получения точных результатов для текста реального слова.
(Если вы хотите получить ответ на свой исходный вопрос, в котором не используется частота слов, вам необходимо уточнить, что именно означает «самое длинное слово»: лучше ли иметь слово из 20 букв и десять слов из 3 букв, или лучше иметь пять слов из 10 букв? Как только вы определитесь с точным определением, вам просто нужно изменить определение линии,
wordcost
чтобы отразить предполагаемое значение.)Идея
Наилучший способ продолжить - смоделировать распределение выпуска. Хорошее первое приближение - это предположить, что все слова распределены независимо. Тогда вам нужно только знать относительную частоту всех слов. Разумно предположить, что они следуют закону Ципфа, то есть слово с рангом n в списке слов имеет вероятность примерно 1 / ( n log N ), где N - количество слов в словаре.
После того, как вы зафиксировали модель, вы можете использовать динамическое программирование для определения положения пробелов. Наиболее вероятное предложение - это предложение, которое максимизирует произведение вероятности каждого отдельного слова, и его легко вычислить с помощью динамического программирования. Вместо прямого использования вероятности мы используем стоимость, определяемую как логарифм обратной величины вероятности, чтобы избежать переполнения.
Код
который вы можете использовать с
Результаты
Я использую этот быстрый и грязный словарь из 125 тысяч слов, который я собрал из небольшого подмножества Википедии.
Как видите, он по сути безупречен. Самая важная часть - убедиться, что ваш список слов был обучен корпусу, аналогичному тому, с которым вы действительно столкнетесь, иначе результаты будут очень плохими.
Оптимизация
Реализация потребляет линейное количество времени и памяти, поэтому она достаточно эффективна. Если вам нужно дополнительное ускорение, вы можете построить дерево суффиксов из списка слов, чтобы уменьшить размер набора кандидатов.
Если вам нужно обработать очень большую последовательную строку, было бы разумно разделить строку, чтобы избежать чрезмерного использования памяти. Например, вы можете обрабатывать текст блоками по 10000 символов плюс поле в 1000 символов с каждой стороны, чтобы избежать граничных эффектов. Это сведет использование памяти к минимуму и почти наверняка не повлияет на качество.
источник
pip install wordninja
words.txt
содержит "comp": `` `$ grep" ^ comp $ "words.txt comp` `` и отсортирован по алфавиту. в этом коде предполагается, что он отсортирован по убыванию частоты появления (что характерно для подобных списков n-граммов). если вы используете правильно отсортированный список, ваша строка получится нормально: `` `>>> wordninja.split ('namethecompanywherebonniewasemployedwhenwestarteddating') ['name', 'the', 'company', 'where', 'bonnie', ' был ',' работал ',' когда ',' мы ',' начал ',' встречался '] ``Основываясь на отличной работе в верхнем ответе , я создал
pip
пакет для удобного использования.Для установки запустите
pip install wordninja
.Единственные отличия незначительны. Это возвращает a,
list
а не astr
, он работаетpython3
, он включает список слов и правильно разбивается, даже если есть не-альфа-символы (например, подчеркивания, тире и т. Д.).Еще раз спасибо Generic Human!
https://github.com/keredson/wordninja
источник
Вот решение с использованием рекурсивного поиска:
дает
источник
Используя структуру данных trie , которая содержит список возможных слов, было бы не слишком сложно сделать следующее:
источник
"tableprechaun"
затем разделить ее"tab"
."tableprechaun"
самое длинное совпадение с самого начала - это"table"
уход"prechaun"
, который нельзя разбить на слова из словаря. Таким образом, вы должны выбрать более короткий матч, в"tab"
результате чего у вас останется"leprechaun"
.Решение Unutbu было довольно близким, но я считаю, что код трудно читать, и он не дал ожидаемого результата. Недостаток решения Generic Human состоит в том, что ему нужны частоты слов. Не подходит для всех вариантов использования.
Вот простое решение с использованием алгоритма «разделяй и властвуй» .
find_words('cupboard')
будет возвращать ,['cupboard']
а не['cup', 'board']
(при условии , чтоcupboard
,cup
иboard
находятся в dictionnary)find_words('charactersin')
['characters', 'in']
['character', 'sin']
(как показано ниже). Вы можете легко изменить алгоритм, чтобы получить все оптимальные решения.Код:
На моем компьютере с частотой 3 ГГц это займет около 5 секунд:
источник
Ответ https://stackoverflow.com/users/1515832/generic-human великолепен. Но лучшая реализация этого, которую я когда-либо видел, была написана самим Питером Норвигом в его книге «Красивые данные».
Прежде чем я вставлю его код, позвольте мне объяснить, почему метод Норвига более точен (хотя и немного медленнее и длиннее с точки зрения кода).
1) Данные немного лучше - как с точки зрения размера, так и с точки зрения точности (он использует подсчет слов, а не простое ранжирование) 2) Что еще более важно, логика, лежащая в основе n-граммов, действительно делает подход настолько точным .
Пример, который он приводит в своей книге, - это проблема разделения струны «сидя». Теперь небиграммный метод разделения строк будет рассматривать p ('sit') * p ('down'), и если это меньше, чем p ('sitdown') - что будет иметь место довольно часто - он НЕ будет разбивать это, но мы бы хотели (большую часть времени).
Однако, когда у вас есть модель биграммы, вы можете оценить p («сесть») как биграмму против p («сесть»), и первая победит. По сути, если вы не используете биграммы, он рассматривает вероятность разбиения слов как независимых, что не так, некоторые слова с большей вероятностью появятся одно за другим. К сожалению, это также слова, которые во многих случаях часто слипаются и сбивают с толку сплиттер.
Вот ссылка на данные (это данные для трех отдельных проблем, а сегментация только одна. Пожалуйста, прочтите главу для подробностей): http://norvig.com/ngrams/
а вот ссылка на код: http://norvig.com/ngrams/ngrams.py
Эти ссылки были доступны некоторое время, но я все равно скопирую и вставлю сюда часть кода сегментации.
источник
RuntimeError: maximum recursion depth exceeded in cmp
Вот принятый ответ, переведенный на JavaScript (требуется node.js и файл "wordninja_words.txt" из https://github.com/keredson/wordninja ):
источник
Если вы предварительно скомпилируете список слов в DFA (что будет очень медленно), то время, необходимое для сопоставления ввода, будет пропорционально длине строки (на самом деле, только немного медленнее, чем просто итерация по строке).
По сути, это более общая версия алгоритма trie, о котором упоминалось ранее. Я упоминаю это только для полноты - пока нет реализации DFA, которую можно было бы просто использовать. RE2 будет работать, но я не знаю, позволяют ли привязки Python настраивать размер DFA до того, как он просто выбрасывает скомпилированные данные DFA и выполняет поиск NFA.
источник
Похоже, достаточно обыденного возврата. Начните с начала строки. Сканируйте прямо, пока не услышите слово. Затем вызовите функцию для остальной части строки. Функция возвращает «false», если она просматривает полностью вправо, не распознав ни слова. В противном случае возвращает найденное слово и список слов, возвращенных рекурсивным вызовом.
Пример: «яблочный стол». Находит «tab», затем «leap», но нет слова в «ple». Другого слова в слове «leapple» нет. Находит «стол», затем «приложение». «ле» ни слова, поэтому пробует яблоко, распознает, возвращает.
Чтобы получить как можно дольше, продолжайте работать, только выдавая (а не возвращая) правильные решения; затем выберите оптимальный по любому выбранному вами критерию (maxmax, minmax, average и т. д.)
источник
На основе решения unutbu я реализовал версию Java:
Вход:
"tableapplechairtablecupboard"
Вывод:
[table, apple, chair, table, cupboard]
Вход:
"tableprechaun"
Вывод:
[tab, leprechaun]
источник
Для немецкого языка есть CharSplit, который использует машинное обучение и неплохо работает со строками из нескольких слов.
https://github.com/dtuggener/CharSplit
источник
Расширяя предложение @miku об использовании a
Trie
, добавление только для добавленияTrie
относительно просто реализовать вpython
:Затем мы можем построить
Trie
словарь на основе набора слов:В результате получится дерево, которое выглядит следующим образом (
*
указывает начало или конец слова):Мы можем включить это в решение, объединив его с эвристикой о том, как выбирать слова. Например, мы можем предпочесть более длинные слова более коротким словам:
Мы можем использовать эту функцию так:
Потому что мы сохраняем нашу позицию в ,
Trie
как мы искать дольше и более длинные слова, обходtrie
не более одного раза за возможным решение (а не2
раз дляpeanut
:pea
,peanut
). Последнее короткое замыкание избавляет нас от хождения по струне в худшем случае.Конечный результат - это всего лишь несколько проверок:
Преимущество этого решения заключается в том, что вы очень быстро узнаете, существуют ли более длинные слова с заданным префиксом, что избавляет от необходимости исчерпывающего тестирования комбинаций последовательностей по словарю. Это также позволяет добраться до
unsolvable
ответа сравнительно дешевым по сравнению с другими реализациями.Недостатками этого решения являются большой объем памяти
trie
и затраты на предварительнуюtrie
сборку.источник
Если у вас есть исчерпывающий список слов, содержащихся в строке:
word_list = ["table", "apple", "chair", "cupboard"]
Использование понимания списка для перебора списка, чтобы найти слово и сколько раз оно встречается.
Функция возвращает
string
вывод слов в порядке спискаtable table apple chair cupboard
источник
Большое спасибо за помощь в https://github.com/keredson/wordninja/
Небольшой вклад того же самого в Java с моей стороны.
Открытый метод
splitContiguousWords
может быть встроен с двумя другими методами в класс, имеющий файл ninja_words.txt в том же каталоге (или изменен по выбору кодировщика). И методsplitContiguousWords
можно было использовать для этой цели.источник
public
метод принимает предложение типа,String
которое разбивается на основе первого уровня с регулярным выражением. Списокninja_words
доступен для скачивания в репозитории git.Это поможет
источник
Вам необходимо определить свой словарный запас - возможно, подойдет любой бесплатный список слов.
После этого используйте этот словарь для построения дерева суффиксов и сопоставьте свой поток ввода с ним: http://en.wikipedia.org/wiki/Suffix_tree
источник