Naiwny algorytm nie da dobrych wyników, gdy zostanie zastosowany do rzeczywistych danych. Oto 20-liniowy algorytm, który wykorzystuje względną częstotliwość słów w celu uzyskania dokładnych wyników dla tekstu rzeczywistego.
(Jeśli chcesz odpowiedzieć na swoje pierwotne pytanie, które nie używa częstotliwości słów, musisz sprecyzować, co dokładnie oznacza „najdłuższe słowo”: czy lepiej mieć 20-literowe słowo i dziesięć 3-literowych, czy też lepiej mieć pięć 10-literowych słów? Gdy już zdecydujesz się na precyzyjną definicję, wystarczy zmienić definicję linii, wordcost
aby odzwierciedlić zamierzone znaczenie.)
Pomysł
Najlepszym sposobem postępowania jest modelowanie rozkładu wyników. Dobrym pierwszym przybliżeniem jest założenie, że wszystkie słowa są rozmieszczone niezależnie. Wtedy wystarczy znać względną częstotliwość wszystkich słów. Można założyć, że są zgodne z prawem Zipfa, to znaczy słowo o randze n na liście słów ma prawdopodobieństwo około 1 / ( n log N ), gdzie N to liczba słów w słowniku.
Po ustaleniu modelu można użyć programowania dynamicznego, aby wywnioskować położenie przestrzeni. Najbardziej prawdopodobne zdanie to takie, które maksymalizuje iloczyn prawdopodobieństwa każdego pojedynczego słowa i można je łatwo obliczyć za pomocą programowania dynamicznego. Zamiast bezpośrednio korzystać z prawdopodobieństwa, używamy kosztu zdefiniowanego jako logarytm odwrotności prawdopodobieństwa, aby uniknąć przepełnienia.
Kod
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))
z którym możesz korzystać
s = 'thumbgreenappleactiveassignmentweeklymetaphor'
print(infer_spaces(s))
Wyniki
Używam tego szybkiego i nieczytelnego słownika na 125 000 słów, który stworzyłem na podstawie niewielkiego podzbioru Wikipedii.
Przed: kciukzielonyappleaktywne zadanieweeklymetafora.
Po: kciuk zielone jabłko aktywne zadanie cotygodniowa metafora.
Przed: istnieje wiele informacji tekstowych narodów, które zostały wyodrębnione z htmlbuttherearen od ograniczonych znaków w nich przed badaniem grubozielonych jabłek, aktywnych zadań tygodniowo, metaforę, wydaje się, że istnieje wiele zielonych jabłek, które można znaleźć w jednym z nich, tak więc trzeba było wyskoczyć na pytanie, niezależnie od tego, czy jest to rozsądny przedmiot w słowniczku.
Po: jest mnóstwo informacji tekstowych komentarzy ludzi, które są analizowane z html, ale nie ma w nich znaków rozdzielonych, na przykład kciuk zielone jabłko aktywne przypisanie cotygodniowa metafora najwyraźniej jest kciuk zielone jabłko itp. W ciągu mam również duży słownik do zapytaj, czy słowo jest rozsądne, więc jaki jest najszybszy sposób wyodrębnienia thx dużo.
Przedtem: w nocy, w nocy, w pociągu spotykał się i burzy, akceptowano sporadyczne odstępy czasu, gdy był on kontrolowany przez gwałtowny podmuch wiatru, który trzepotał ulicami wywołującymi zapalenie, gdy ten cudowny widok wywoływał brzęczenie w pobliżu domków i gwałtowniej potrząsając potwornym ogniem.
Potem: była ciemna i burzowa noc, deszcz padał ulewami, z wyjątkiem sporadycznych okresów, kiedy był zatrzymywany przez gwałtowny podmuch wiatru, który przetoczył się ulicami, ponieważ to w Londynie nasza scena grzechota wzdłuż dachów i gwałtownie wstrząsa skąpe płomienie lamp, które walczyły z ciemnością.
Jak widać, jest w zasadzie bezbłędny. Najważniejszą częścią jest upewnienie się, że twoja lista słów została przeszkolona w korpusie podobnym do tego, z czym faktycznie się spotkasz, w przeciwnym razie wyniki będą bardzo złe.
Optymalizacja
Implementacja zużywa liniowo ilość czasu i pamięci, więc jest dość wydajna. Jeśli potrzebujesz dalszego przyspieszenia, możesz zbudować drzewo sufiksów z listy słów, aby zmniejszyć rozmiar zestawu kandydatów.
Jeśli chcesz przetworzyć bardzo duży ciąg ciągów kolejnych, rozsądne byłoby podzielenie ciągu, aby uniknąć nadmiernego wykorzystania pamięci. Na przykład, możesz przetworzyć tekst w blokach po 10000 znaków plus margines 1000 znaków po obu stronach, aby uniknąć efektów brzegowych. Pozwoli to zminimalizować użycie pamięci i prawie na pewno nie będzie miało wpływu na jakość.