Jakiej struktury danych użyć – slownik T9

0

Nie pisałem, że Twój ma O(log n), tylko że MÓJ, który zaproponowałem (opisowo) kilka postów wyżej.
I tam jest trie lub tablica posortowana zbudowana ze słów przetłumaczonych na kody klawiszy, powiązane później z właściwymi słowami.

To jest różnica. Ale swoją drogą w przypadku trie, to chyba w tej swojej analizie przesadziłeś z wykładniczą złożonością. Nawet jak zrobisz trie na literkach, a nie klawiszach, to i tak przeglądanie wszystkich adekwatnych ścieżek nie będzie wykładnicze, bo... większości ścieżek w trie zwyczajnie nie będzie. Więc Twoja analiza jest błędna. Pisałem już, że generowanie WSZYSTKICH przedrostków jest głupie i nie będzie działać.

@Azarien: w przypadku kilkuset tysięcy słów maks. 25 znakowych kwestia czy złożoność jest wykładnicza, czy logarytmiczna, na powolnym procesorze komórki ma bardzo duże znaczenie. I czy wyszukiwanie wyrazu będzie liniowe czy logarytmiczne też.

// Dopisane.
Poniżej kompletna implementacja T9 w 4 linijkach (jeśli nie liczyć nagłówków funkcji).

Jeśli n - liczba słów, to:
Złożoność budowania słownika: O(n log n)
Złożoność wyszukiwania pierwszego pasującego słowa: O(log n)
Złożoność przechodzenia do następnego znalezionego słowa: O(1)
Zajętość pamięciowa słownika: O(n)
Złożoność wyszukiwania nie zależy od długości wyrazów.


  import collection.immutable.{SortedMap, TreeMap}

  val chars =  Map() ++ (('a' to 'z' toList) zip List(2,2,2,3,3,3,4,4,4,5,5,5,6,6,6,7,7,7,7,8,8,8,9,9,9,9))

  def translate(s: String): String = 
    s.toArray map chars mkString

  def buildTree(words: List[String]): SortedMap[String, String] =
    TreeMap[String, String]() ++ (words map (w => (translate(w) + w) -> w))

  def find(tree: SortedMap[String, String], prefix: String): Iterable[String] =       
    tree from prefix until (prefix + '\u6666') values
  

Test:

scala> val t = buildTree(List("moja", "mama", "ma", "nowego", "kota"))
t: scala.collection.immutable.SortedMap[String,String] = Map((5682,kota), (62,ma), (6262,mama), (6652,moja), (669346,nowego))

scala> find(t, "6").toList
res34: List[String] = List(ma, mama, moja, nowego)

scala> find(t, "62").toList
res35: List[String] = List(ma, mama)

scala> find(t, "66").toList
res36: List[String] = List(moja, nowego)

scala> find(t, "669").toList
res37: List[String] = List(nowego)

scala> find(t, "665").toList
res38: List[String] = List(moja)

scala> find(t, "6659").toList
res39: List[String] = List()

Modyfikację tak, aby działały priorytety pozostawiam jako pracę domową. :P

0
Krolik napisał(a)

To jest różnica. Ale swoją drogą w przypadku trie, to chyba w tej swojej analizie przesadziłeś z wykładniczą złożonością. Nawet jak zrobisz trie na literkach, a nie klawiszach, to i tak przeglądanie wszystkich adekwatnych ścieżek nie będzie wykładnicze, bo... większości ścieżek w trie zwyczajnie nie będzie. Więc Twoja analiza jest błędna. Pisałem już, że generowanie WSZYSTKICH przedrostków jest

Dziekuje bardzo za odpowiedz.
Mam jeszcze jedno pytanie, ddnosnie Twojej cytowanej wypowiedzi powyzej:

Tylko ze moj program najpierw generuje wszsystkie przedrostki, a potem je sprawdza.
Samo generowanie wiec musi byc wykladnicze w moim przypadku, prawda?
A swiadczyc o tym moze tez zawieszenie programu przy wpisaniu 13-14 znaku (przy wpisaniu OutOfMemory)

Pewnie mozna bylo to ograniczyc poprzez jakas kontrole, ktora by zapobiegala generowaniu prefiksow, ktore nie tworza wyrazy, czyli np: kilku spolglosek obok siebie 'ktbdst' etc... lub zaimplementowac wedlugo Twojego sposobu.

0

Tak, masz rację. Generowanie wszystkich ścieżek daje wykładniczą.
Ale taki sposób to już najgorszy z możliwych jest. Bo jak nie znajdziesz ścieżki "jk", to po co sprawdzać ścieżki dłuższe?

0
Krolik napisał(a)

Tak, masz rację. Generowanie wszystkich ścieżek daje wykładniczą.
Ale taki sposób to już najgorszy z możliwych jest. Bo jak nie znajdziesz ścieżki "jk", to po co sprawdzać ścieżki dłuższe?

Dzieki bardzo.

Gdybys mial jeszcze chwile bede niezmienie wdzieczny gdybys mogl odpowiedziec dlaczego np: struktura danych HashTable lub ArrayList nie byla lepsza od Trie do implementacji slownika.

Czy tylko przez to, ze Trie ma mniejsza zlozosc, i najgorszym przypadku szukanie slow zajmuje tyle ile dlugosc najdluzszego slowa w slowniku.
A w przypadku HashTable, w najgorszym wypadku (przy wielu kolizjach) bedzie liniowe?

pozdrawiam i z gory dzieki

0

HashTable w ogóle odpada, bo musisz wyszukiwać po prefiksach. Jak policzysz hashcode?

ArrayList jest ok, tylko musi być posortowany. Złożoność podobna do rozwiązania, które podałem.
Problem jednak jest przy dodawaniu nowego słowa -> złożoność O(n), żeby zachować upoirządkowanie.

Drzewo czerwono-czarne (moje rozw.) ma dobrą złożoność i zarazem można dodawać nowe wyrazy w O(log n).

Trie podobnie, z tą różnicą, że będzie nieznacznie głębszy, ale z kolei tańsze będą porównania. Bez pomiaru trudno coś powiedzieć. Za to na pewno jest dłuższy w implementacji niż wzięcie gotowej implementacji drzewa czerwono-czarnego (w postaci TreeMap), którą powinna mieć każda porządna biblioteka standardowa.

Ty to masz na pracę domową z analizy algorytmów czy co?

0

super dzieki.
czyli HashTable byloby dobre do wyszukania konretnych danych (jesli wiemy co chcemy szukac), a nie jakichs prefiksow wyrazow, mam racje?

pozdrawiam

0

Tak.

BTW: A tak swoją drogą, to ciekaw jestem, czy w Perlu, Pythonie albo Rubym da się napisać jeszcze krótszą implementację T9 niż 8 linijek.

0
Krolik napisał(a)

Tak.

BTW: A tak swoją drogą, to ciekaw jestem, czy w Perlu, Pythonie albo Rubym da się napisać jeszcze krótszą implementację T9 niż 8 linijek.

Ja na to pytanie na pewno nie odpowiem :P
Znam tylko Jave, a wlasciwie zaczynam poznawac :D

0

W Javie to się akurat na pewno nie da napisać krótszej.
BTW: Prawdziwe T9 stosuje jeszcze jakąś sprytną (opatentowaną) kompresję wyrazów, więc pewnie nie używa prostego drzewa czy tablicy:

Wikipedia napisał(a)

In order to achieve compression ratios of close to 1 byte per word, T9 uses an optimized algorithm which maintains the order of words, and partial words (also known as stems) but because of this compression, it over-generates words which are sometimes visible to the user as 'junk words'. This is a side effect of the requirements for small database sizes on the lower end embedded devices.

0

Tak, ten algorytm opiera sie tez na n-gramach (na poziomie slownym i literowym), czyli prawdopodobienstwie nastepnego slowa/nastepnej litery na podstawie poprzednich slow/liter w danym jezyku.

Samego takiego rozwiazania uzywa LetterWise / WordWise by Eatoni Ergonomic - taka jakby konkurencja dla T9.

Ale lepszy hardcore jest z opracowaniem dobrego algorytmu (podpowiadania wyrazow) dla jezykow fleksyjnych (takich jak np: polski czy wloski, gdzie wyraz w zaleznosci od: czasu, osoby, rodzaju etc... ma inna koncowke).
W tym wypadku T9 jest duzo wolniejszy niz np: T9 dla jezyka angielskiego, ktory nie nalezy do jezykow fleksyjnych.

Wlosi cos takiego stworzyli, nazywa sie FastType: http://www.ijcis.info/Vol5N2/Vol5N2PP79-85.pdf

pozdrawiam

0

nie mozna tego zrobic tak zwykle binarnie?
t
-a
--t
---a(znak specjalny do 3poziomów w dół)
-o
--r
---g
----a
-----n8(znak konca frazy)
------y(uznajemy za dopełnienie)(*koniec frazy)

dla pierwszych znaków alfabetu a - ż dajesz index ze od tego miejsca sie zaczyna.
Liczysz obiekty w danej gałęzi, ilośc wystąpień mozesz sobie zapisywac obok znaku końca frazy.
drzewko:
Level(BYTE)|UTF_CHAR(MULTIBYTE)|CONTROL_SIGN(BYTE)|POPULARITY(2-4 BYTES)

mało pamięci, można indexować, można liczyc, proc da sobie rade.
Ja rozumiem że Javowcy uwielbiają wyrafinowane struktury, ja jednak wciąz uznaje że suche podejście stanów 0 i 1 z ewentualna konwersją dziesiętną popłaca.

0

zrob sobie proste drzewo, tj. zaczynasz od wspolnego ojca SLOWNIK,
SLOWNIK laczy sie z poszczegolnymi literkami, kazda z tych literek laczy sie z nastepnymi literkami, z tym, ze jesli istnieje wyraz konczacy sie na danej literce, to np. sposobem zapisania jego wystepowania bedzie ilosc uzyc=1, jesli nie istnieje, to ilosc uzyc=0,

zrobilem przykladowy graf, ktory moglby reprezentowac to, co mam na mysli.
http://graph.gafol.net/bJYboqVZx

teraz jesli szukasz wszystkich wyrazow, to nie przeszukujesz calego slownika, tylko wszystkie dzieci od tego momentu, gdzie doszedles w pisaniu, z tego wyznaczasz najczestrze slowa etc. jesli ich jest tylko 5k, to mozesz nawet co literke od nowa wyznaczac je, nic sie nie stanie.

graph t9 {

slownik--s
s--se
se--sex
se--sek
sek--seks_uzyte100000

slownik--p
p--pl
 pl--ple
  ple--plec_uzyte1
   plec_uzyte1--plecy_uzyte10

slownik--d
d--da
 da--dam
  dam--dama_uzyte2
  dam--damy_uzyte1
  dam--damn_uzyte100
 da--das
  das--dasz_uzyte23
   dasz_uzyte23--dasze
    dasze--daszek_uzyte1
   dasz_uzyte23--daszy
    daszy--daszys
     daszys--daszysk
      daszysk--daszysko_uzyte2
}
`</code>`
0

Nie ma to jak ciągnąć już rozwiązany temat nic nowego nie wnosząc i przedstawiając niekompletne rozwiązania. W T9 wprowadzane są cyfry/kody klawiszy, a nie początki wyrazów. Więc drzewa zbudowane z samych literek są bez sensu, chyba ze ktoś chce sobie wyszukiwanie komplikować.

Ja rozumiem że Javowcy uwielbiają wyrafinowane struktury, ja jednak wciąz uznaje że suche podejście stanów 0 i 1 z ewentualna konwersją dziesiętną popłaca.

TreeMap z biblioteki standardowej + 4 linie kodu do jego obsługi nazywasz wyrafinowaną strukturą? 8-O
Swoją drogą nie widzę związku między konwersją dziesiętną, stanami 0-1 a słownikiem T9... To dopiero jakiś wyrafinowany algorytm musi być. Mógłbyś przybliżyć?

0

Królik, chodziło mi nie o samo rozwiązanie ale o bajtowe podejście ;) troche sie obijam o C oraz asm wiec podejście pod tytułem uzyj klasy jest lekko nienaturalne.

Zarejestruj się i dołącz do największej społeczności programistów w Polsce.

Otrzymaj wsparcie, dziel się wiedzą i rozwijaj swoje umiejętności z najlepszymi.