Наївний алгоритм не дасть хороших результатів при застосуванні до реальних даних. Ось 20-рядковий алгоритм, який використовує відносну частоту слів, щоб дати точні результати для тексту в реальному слові.
(Якщо ви хочете відповісти на своє початкове запитання, в якому не використовується частота слів, вам потрібно уточнити, що саме означає "найдовше слово": чи краще мати 20-літерне слово та десять 3-літерних слів, чи це краще мати п’ять букв з десяти літер? Після того, як ви зупинитесь на точному визначенні, вам просто потрібно змінити рядок, що визначає, wordcost
щоб відобразити передбачуваний зміст.)
Ідея
Найкращий спосіб продовжити - моделювати розподіл результатів. Хорошим першим наближенням є припущення, що всі слова розподілені незалежно. Тоді вам потрібно лише знати відносну частоту всіх слів. Доцільно припустити, що вони відповідають закону Зіпфа, тобто слово з рангом n у списку слів має ймовірність приблизно 1 / ( n log N ), де N - кількість слів у словнику.
Після того як ви зафіксували модель, ви можете використовувати динамічне програмування, щоб визначити положення пробілів. Найбільш вірогідне речення - це те, що максимально добуває ймовірність кожного окремого слова, і його легко обчислити за допомогою динамічного програмування. Замість того, щоб безпосередньо використовувати ймовірність, ми використовуємо витрати, визначені як логарифм зворотної ймовірності, щоб уникнути переповнення.
Код
from math import log
# Build a cost dictionary, assuming Zipf's law and cost = -math.log(probability).
words = open("words-by-frequency.txt").read().split()
wordcost = dict((k, log((i+1)*log(len(words)))) for i,k in enumerate(words))
maxword = max(len(x) for x in words)
def infer_spaces(s):
"""Uses dynamic programming to infer the location of spaces in a string
without spaces."""
# Find the best match for the i first characters, assuming cost has
# been built for the i-1 first characters.
# Returns a pair (match_cost, match_length).
def best_match(i):
candidates = enumerate(reversed(cost[max(0, i-maxword):i]))
return min((c + wordcost.get(s[i-k-1:i], 9e999), k+1) for k,c in candidates)
# Build the cost array.
cost = [0]
for i in range(1,len(s)+1):
c,k = best_match(i)
cost.append(c)
# Backtrack to recover the minimal-cost string.
out = []
i = len(s)
while i>0:
c,k = best_match(i)
assert c == cost[i]
out.append(s[i-k:i])
i -= k
return " ".join(reversed(out))
якими ви можете користуватися
s = 'thumbgreenappleactiveassignmentweeklymetaphor'
print(infer_spaces(s))
Результати
Я використовую цей швидкий і брудний словник в 125 тисяч слів, який я зібрав з невеликої підмножини Вікіпедії.
Перед: велике пальцева активність присвоєнняметометафора.
Після: великий палець зеленого яблука активне призначення щотижневої метафори.
Перш: тудиспроможнешлякштекнформація людей, що коментують, щовирішенезмінено, апростуватимемохарактеристикипредприміткомпредставленнямпунктивуєпризначенняммехафото, швидше, зрештою, змайструватимете папкузреагувати шрифт на голову.
Після: є маса текстової інформації коментарів людей, яка розбирається з html, але в них немає обмежених символів, наприклад, великий палець зеленого яблука активне призначення щотижневої метафори, мабуть, є великий палець зелене яблуко тощо в рядку, у мене також є великий словник для запитайте, чи це слово розумне, і який найшвидший спосіб видобування thx багато.
Попередньо : це булопарковою вечірньою вечіршньою фальшивою ділянкою зафіксованих випадкових інтервалів, які перевірялися під час віолементарного переходу вітрів, що передбачає найголовніші рейтинги, проголошення, що увімкняться, умиротворені довгі будинки, ідифтер, що рухаються, уважаючи, що це означає, що у них буде вигляд
Після: була темна і бурхлива ніч, дощ випав у потоках, за винятком періодичних проміжок часу, коли його перевіряли бурхливі пориви вітру, які прокотилися вулицями, адже саме в Лондоні наша сцена лежить грохотом по хатах і люто хвилюється мізерне полум’я ламп, що боролися проти темряви.
Як бачите, це по суті бездоганно. Найважливіша частина - переконатися, що ваш список слів був привчений до корпусу, схожого на те, що ви насправді зіткнетеся, інакше результати будуть дуже поганими.
Оптимізація
Реалізація вимагає лінійної кількості часу та пам'яті, тому вона є досить ефективною. Якщо вам потрібні подальші прискорення, ви можете створити суфіксне дерево зі списку слів, щоб зменшити розмір набору кандидатів.
Якщо вам потрібно обробити дуже великий послідовний рядок, було б розумно розділити рядок, щоб уникнути зайвого використання пам'яті. Наприклад, ви можете обробити текст блоками по 10000 символів плюс запас 1000 символів з обох сторін, щоб уникнути граничних ефектів. Це дозволить мінімізувати використання пам’яті і майже не матиме жодного впливу на якість.