Præsentation er lastning. Vent venligst

Præsentation er lastning. Vent venligst

1 Strengbehandling. 2 Plan Strengsøgning - Knuth-Morris-Pratt-algoritmen endelige tilstandsmaskiner - Boyer-Moore-algoritmen - Rabin-Karp-algoritmen Syntaksanalyse.

Lignende præsentationer


Præsentationer af emnet: "1 Strengbehandling. 2 Plan Strengsøgning - Knuth-Morris-Pratt-algoritmen endelige tilstandsmaskiner - Boyer-Moore-algoritmen - Rabin-Karp-algoritmen Syntaksanalyse."— Præsentationens transcript:

1 1 Strengbehandling

2 2 Plan Strengsøgning - Knuth-Morris-Pratt-algoritmen endelige tilstandsmaskiner - Boyer-Moore-algoritmen - Rabin-Karp-algoritmen Syntaksanalyse (parsing) og oversættelse - syntaksanalyse ved rekursiv nedstigning - oversættelse

3 3 En streng er en lineær sekvens af “tegn”. En tekststreng er en streng bestående af bogstaver, tal og specialtegn. En binær streng er en streng bestående af værdierne 0 og 1. Givet en streng på N tegn, a, kaldet teksten, og en streng på M tegn, p, kaldet mønsteret. Typisk er N >> M. Eksistens: Forekommer p i a ? Optælling: Hvor mange gange forekommer p i a ? Søg: Find en forekomst af p i a. Søg-alle: Find alle forekomster af p i a. Strengsøgning

4 4 Eksempler på strengsøgning Binær streng: find 101001111010111011 i 100010010101101000100110101101011011011 001111111101010011101010000000101111010011011 10000001100011000010011000011110110111000000 110111101011111011110001111000011011111001110 011001111011001000000011101111110000101100111 001101001011010100100100011111111110111001111 01100101010011110111100111011001001001001100 01000100111000010000011100010101110101001101 100100011 Tekststreng: find gjkmxorzeoa i kvjlixapejrbxeenpphkhthbkwyrwamnugzhppfxiyjya nhapfwbghxmshwrlyujfjhrsovkvveylnbxnawavgizyv mfohigeabgksfnbkmffxjffqbualeytqrphyrbjqdjqavct gxjifqgfgydhoiwhrvwqbxgrixydzbpajnhopvlamhhfa voctdfytvvggikngkwzixgjtlxkozjlefilbrboignbzsudss vqymnapbpqvlubdoyxkkwhcoudvtkmikansgsutdjyt hzlapawlvliygjkmxorzeoafeoffbfxuhkzukeftnrfmocyl culksedgrdivayjpgkrtedehwhrvvbbltdkctq

5 5 Find Hooligan i Hoola-Hoola girls like Hooligans Hooligan.... Hooligan I alt: 39 tegnsammenligninger Eksempel på strengsøgning

6 6 Check, for hver mulig position i teksten, om mønsteret matcher fra denne position og fremad. Strengsøgning ved rå kraft int brutesearch(char p[], char a[]) { int i, j, M = p.length, N = a.length; for (i = 0; i <= N-M; i++) { for (j = 0; j < M; j++) if (p[j] != a[i+j]) break; if (j == M) return i;} return N; } 0ii+MN 0M a: p: ≤i+j j

7 7 match: forøg både i og j med 1. mismatch: sæt i tilbage, og sæt j til 0. Bemærk fejl i lærebogen! ( i -= j-1 skal være i -= j ) for (i = j = 0; i < N && j < M; i++, j++) if (p[j] != a[i]) { i -= j; j = -1; } return j == M ? i-M : N; Anden implementation (samme algoritme) 0i-j i N 0M a: p: j

8 8 Analyse af brutesearch Køretid afhænger af tekst og mønster (2) Gennemsnitlige tilfælde (fast mønster, tilfældig tekst): Søg efter 000001 100110100101000101001110000111 * 00* 0* * 0* * 00* Langt mønster: cirka 2*N bitsammenligninger. [ Sedgewick & Flajolet: “Analysis of Algorithms”, 1996 ] (1) Værste tilfælde: Søg efter 000001 000000000000000000000000000001 00000* Cirka M*N bitsammenligninger.

9 9 Strengsøgning i Java public int indexOf(String str, int fromIndex) { char v1[] = value, v2[] = str.value; int max = offset + (count - str.count); if (fromIndex >= count) return -1; if (fromIndex < 0) fromIndex = 0; if (str.count == 0) return fromIndex; int strOffset = str.offset; char first = v2[strOffset]; int i = offset + fromIndex; startSearchForFirstChar: while (true) { while (i <= max && v1[i] != first) i++; if (i > max) return -1; int j = i + 1, end = j + str.count - 1; int k = strOffset + 1; while (j < end) if (v1[j++] != v2[k++]) { i++; continue startSearchForFirstChar; } return i - offset;} }

10 10 Eksempel på søgning Find p = 10100111 a = 10110101001010100111011 1: 10100111 2: 10100111 3: 10100111 4: 10100111 5: 10100111 6: 10100111 7: 10100111 8: 10100111 9: 10100111 10: 10100111 11: 10100111 12: 10100111 13: 10100111 Meget arbejde er overflødigt. For eksempel opdages det 2 gange, at delstrengen 10 er delstreng i teksten (linje 4 og 6). Alle understregede tests er overflødige (17 ud af 37).

11 11 Observationer Vi ønsker i hvert skridt, at skubbe p så langt til højre som muligt. Hvor langt kan bestemmes uafhængigt af a. De sidst læste tegn i a er kendt, da de har matchet p indtil nu. Vi behøver aldrig at gå baglæns i a, kun i p. Det aktuelle tegn i a kan dog sammenlignes med flere tegn i p.

12 12 int kmpsearch(char p[], char a[]) { int i, j, M = p.length, N = a.length; initnext(p); for (i = j = 0; i < N && j < M; i++, j++) while (j >= 0 && p[j] != a[i]) j = next[j]; return j == M ? i-M : N; } Knuth-Morris-Pratt (1970) KMP Lad next[j] angive den position i p, der skal fortsættes med i tilfælde af en mismatch for p[j]. Så kan søgealgoritmen formuleres således: idet indholdet af tabellen next bestemmes inden søgningen (med initnext ), og next[0] = -1.

13 13 Kompleksitet af KMP KMP søgning bruger aldrig mere end 2N tegnsammenligninger. Bevis: For hver værdi af i øges j med 1, eller mindskes ud fra next -tabellen. For hvert i foretages altså højst 2 sammenligninger. 0iN next[j] M a:a: p:p: j i' ≠

14 14 Tabellen next p = 10100111 i next[i] 0 -1 1 01*...... 1....... 2 010*...... 10...... 3 1101*..... 101...... 4 21010*.... 1010.... 5 010100*... 10100... 6 1101001*.. 101001.. 7 11010011*. 1010011.

15 15 Algoritme til bestemmelse af next[i] : Lad en kopi af p ’s første i tegn glide hen over p ’s første i tegn, startende i det andet tegn i p. Stop, hvis alle overlappende tegn matcher. next[i] er da antallet af overlappende tegn. Initialisering af next 0iN a:a: M p:p: j ≠ next[j] M p:p: j next[j] er længden af det længste præfiks i p[0:j- 1], der også er suffiks i p[0:j-1].

16 16 void initnext(char p[]) { int i, j, k, M = p.length; next[0] = -1; for (i = 1; i < M; i++) { for (j = i-1; j > 0; j--) { for (k = 0; k < j && p[k] == p[i-j+k]; k++) ; if (k == j) break; } next[i] = j;} } Simpel initialisering af next j tegn j p:p: p:p: i k0 0Mi-j M

17 17 Mere effektiv initialisering af next next kan bestemmes ved at matche p med sig selv. Snedig algoritme! i j tegn j p:p: p:p: 0 i-j Lige før tildelingen next[i] = j har vi, at de første j tegn i p, dvs. p[0],..., p[j-1], matcher de sidste j tegn af de første i tegn i p, dvs. p[i-j ],..., p[i-1 ]. void initnext(char p[]) { int i, j, M = p.length; next[0] = -1; for (i = 0, j = -1; i < M; i++, j++, next[i] = j) while (j >= 0 && p[j] != p[i]) j = next[j]; }

18 18 Algoritmen i initnext tager ikke årsagen til en mismatch i betragtning. Hvis teksten f.eks. starter med 1011, og vi søger efter 10100111, siger next[3], at vi næste gang skal prøve at matche med p[1]. Men vi ved, at denne match vil mislykkes. Derfor bør next[3] i stedet for være 0 (= next[1] ). Forbedring af initnext (Knuth) Løkken i initnext ændres til for (i = 0, j = -1; i < M; i++, j++, next[i] = p[i] == p[j] ? next[j] : j) if (j >= 0 && p[j] != p[i]) j = next[j]; i j tegn j p:p: p:p: 0 i-j p[i] != p[j]

19 19 Forbedring af tabellen next p = 10100111 i next[i] (før)next[i] (nu) 0 -1 -1 1 01*...... 0 1....... 2 010*...... -1 10...... 3 1101*..... 0 101...... 4 21010*.... 2 1010.... 5 010100*... -1 10100... 6 1101001*.. 1 101001.. 7 11010011*. 1 1010011.

20 20 Find Hooligan i Hoola-Hoola girls like Hooligans Hooligan.... Hooligan I alt: 33 tegnsammenligninger (39 med brutesarch ) Eksempel på KMP-søgning

21 21 Endelige tilstandsmaskiner En endelig tilstandsmaskine (FSA = Finite State Automaton) er karakteriseret ved 1. en endelig mængde af tilstande 2. en starttilstand 3. en eller flere sluttilstande 4. en endelig mængde af inputsymboler 5. en funktion, move, der afbilder mængden af par, bestående af et inputsymbol og en tilstand, på mængden af tilstande move(input, state) state

22 22 En FSA kan repræsenteres ved hjælp af en tabel. move -tabel: input\state 0 1 2 3 4 5 6 7 0 0 2 0 4 5 0 2 2 1 1 1 3 1 3 6 7 8 En FSA til genkendelse af mønsteret 10100111 Starttilstand: 0 Sluttilstand: 8 0 1 2 34 5 6 7 8 1 0 0 10011 1 1 0 1 1 0 0 0 Søgeeksempel: 100111010010100010100111000111 0120111234562345012345678

23 23 (1) Byg en FSA ud fra mønsteret (2) Kør FSA på teksten KMP ved hjælp af FSA Tidsforbrug: O(N). int kmpsearch(char p[], char a[]) { int i, state, M = p.length, N = a.length; initmove(p); for (i = state = 0; i < N && state < M; i++) state = move[a[i] == '0' ? 0 : 1][state]; return state == M ? i-M : N; }

24 24 Bygning af FSA Efterfølgertilstande til tilstand i : * match, så i+1 * mismatch, så den FSA-tilstand, der svarer til “længst mulige match” (tag højde for mismatch) Eks. FSA for 10100111 Tilstand 6 1010011: gå til tilstand 7 1010010: gå til tilstand for 010010 (tilstand 2), husk tilstand for 010011 (X = 1). Tilstand 7 10100111: gå til tilstand 8 10100110: gå til tilstand for 0100110 (tilstand 2), husk tilstand 0100111 (X = 1). (disse er efterfølgere af forrige X)

25 25 void initmove(char p[]) { move = new int[2][p.length]; int X = 0; for (int i = 0; i < p.length; i++) { int match = p[i] == '0' ? 0 : 1; int mismatch = match == 0 ? 1 : 0; move[match][i] = i+1; move[mismatch][i] = move[mismatch][X]; X = move[match][X]; } } initmove() Match-tilstanden i move er unødvendig. Repræsenter kun mismatch-tilstande, next. Herved fremkommer algoritmerne i lærebogen.

26 26 Endelig tilstandsmaskine til indlæsning af decimaltal (f.eks.: -3.1415 ) Start 0 Fortegn læst 1 Læsning af heltalsdel 2 Punktum læst 3 Læsning af decimaldel 4 Slut 5 fortegn ciffer punktum ciffer andet cifferpunktum ciffer andet punktum andet

27 27 int stateTable[][] = {{1,0,0,0,0}, {2,2,2,4,4}, {3,3,3,0,0}, {0,0,5,0,5}}; int charClass(char c) { return c == '+' || c == '-' ? 0 : Character.isDigit(c) ? 1 : c == '.' ? 2 : 3; } Tabelstyret indlæsning af decimaltal Tilstand 0: starttilstand Tilstand 1: fortegn læst Tilstand 2: læsning af heltalsdel Tilstand 3: punktum læst Tilstand 4: læsning af decimaldel Tilstand 5: sluttilstand (tegn af klasse 3 læst)

28 28 float inReal() throws IOException { int state = 0, wholePart = 0, sign = 1; float decimalPart = 0, exp = 0.1; while (true) { char c = (char) System.in.read(); int action = actionTable[charClass(c)][state]; switch(action) { case 0: break; case 1: if (c == '-') sign = -1; break; case 2: wholePart = 10*wholePart + Character.digit(c,10); break; case 3: decimalPart += Character.digit(c,10)*exp; exp *= 0.1; break; case 4: return sign*(wholePart + decimalPart); case 5: throw new IOException("syntax error"); } state = stateTable[charClass(c)][state];} } inReal int actionTable[][] = {{1,5,5,5,5}, {2,2,2,3,3}, {0,0,0,5,5}, {5,5,4,5,4}};

29 29 En af de mest effektive algoritmer til strengsøgning. Benyttes i de fleste tekstredigeringsprogrammer. Eks: Find p = 10100111 i a = 10110101001010100111011 1: 10100111 2: 10100111 3: 10100111 4: 10100111 5: 10100111 6: 10100111 I alt: 16 sammenligninger. KMP: 19 sammenligninger Boyer-Moore (1975) I stedet for (som KMP) at gå fra venstre mod højre i mønsteret kan det ofte bedre betale sig at gå fra højre mod venstre.

30 30 BM kan i lighed med KMP implementeres ved brug af et next -array. Bedre er dog i mange tilfælde at benytte en heuristik, der, givet et mismatchet tegn i teksten samt mønsteret, kan beslutte, hvor mange tegn, der skal springes fremad i teksten. Der springes altid mindst 1 tegn fremad. Implementering

31 31 Eks. Find Hooligan Hoola-Hoola girls like Hooligans Hooligan skip[H] = 7, skip[o] = 5, skip[l] = 4,skip[i] = 3, skip[g] = 2, skip[a] = 1,skip[n] = 0, skip[ alle andre tegn ] = 8. I alt: 12 tegnsammenligninger (33 med KMP) Lad skip[c] indeholde det antal tegn, der skal springes fremad i teksten i tilfælde af, at det aktuelle tegn c i teksten ikke matcher med det aktuelle tegn i mønsteret. Arrayet skip kan bestemmes alene ud fra mønsteret - altså forud for søgningen i teksten. Hvis et tegn c ikke forekommer i mønsteret, er skip[c] lig med mønsterets længde. Ellers er skip[c] lig med det mindste antal tegn, c skal flyttes til højre i mønsteret for at blive det sidste.

32 32 Implementering i Java int bmsearch(char p[], char a[]) { initskip(p); int i, j, M = p.length, N = a.length; for (i = j = M-1; j >= 0; i--, j--) while (a[i] != p[j]) { i += Math.max(skip[a[i]], M-j); if (i >= N) return N; j = M-1; } return i+1; } void initskip(char p[]) { int M = p.length; for (int i = 0; i < 256; i++) skip[i] = M; for (int j = 0; j < M; j++) skip[p[j]] = M-j-1; } Bemærk fejlene i lærebogen.

33 33 Kompleksitet af BM BM bruger aldrig mere end M+N tegnsammen- ligninger, og cirka N/M, hvis alfabetet ikke er lille, og mønsteret er langt. [ Tiden aftager, jo længere mønsteret er! ] Dette gælder dog kun for Boyer og Moores originale algoritme. Den præsenterede algoritme kan i værste fald bruge cirka MN sammenligninger.

34 34 Rabin-Karp (1981) Ide: Brug hashing. * beregn hashværdi for hver mulig deltekst af længde M i teksten * sammenlign med hashværdien for mønsteret * der er ikke behov for nogen hashtabel

35 35 Problem: En fuldstændig sammenligning er nødvendig ved kollision. Afhjælpning: Benyt en meget stor (virtuel) tabelstørrelse. (dog ikke så stor, at der sker aritmetisk overløb). Problem: Hashfunktionen afhænger af M tegn. Løsning: Beregn hashfunktion for i+1 ud fra hash- funktion for i (forbedrer køretiden fra N*M til N+M). Eks: “tabelstørrelse” = 97, M = 5. Find 15926. Hashværdi = 18 (mod 97) 31415926535897932384626433 31415 = 84 (mod 97) 14159 = 94 (mod 97) 41592 = 76 (mod 97) 15926 = 18 (mod 97) 31415 = 84 (mod 97) 14159 = (31415-3*10000)*10 + 9 = (84 - 3*9)*10 + 9 = 579 = 94 (mod 97) 41592 = (94 - 1*9)*10 + 2 = 76 (mod 97) 15926 = (76 - 4*9)*10 + 6 = 18 (mod 97)

36 36 Beregning af hashværdi Antag at en hashværdi er beregnet ud fra h = a[i]d M-1 + a[i+1] d M-2 +... + a[i+M-1], hvor d er antallet af mulige tegn. Den næste værdi af h a[i+1]d M-1 + a[i+2] d M-2 +... + a[i+M-1]d + a[i+M] kan da bestemmes ud fra h: (h - a[i]d M-1 )d + a[i+M]

37 37 Implementering af RK i Java int rksearch(char p[], char a[]) { int q = 3355339, d = 256; int i, dM = 1, h1 = 0, h2 = 0; int M = p.length, N = a.length; for (i = 1; i < M; i++) dM = (d*dM) % q; for (i = 0; i < M; i++) { h1 = (h1*d + p[i]) % q; h2 = (h2*d + a[i]) % q; } for (i = 0; h1 != h2; i++) { h2 = (h2 + d*q - a[i]*dM) % q; h2 = (h2*d + a[i+M]) % q; if (i > N-M) return N; } return i; } NB. Testen h1 != h2 bør udvides til h1 != h2 || !String.valueOf(p). equals(String.valueOf(a,i,M))

38 38 Kompleksitet af RK Rabin-Karp-algoritmen er lineær med meget stor sandsynlighed.

39 39 Empirisk undersøgelse af algoritmernes effektivitet Søgning i “The Oxford English Dictionary” (2nd Edition), cirka 570 millioner tegn. Algoritme “to be or not to be” “data” Rå kraft 1.23 1.74 Knuth-Morris-Pratt2.16 2.93 Boyer-Moore1.33 1.16 Rabin-Karp2.64 3.69 Boyer-Moore-Horspool1.00 1.00 fra G. H. Gonnet & R. Baeza-Yates: Handbook of Algorithms and Data Structures in Pascal and C, Addison-Wesley 1991.

40 40 Boyer-Moore-Horspool int bmhsearch(char p[], char a[]) { int i, j, k; int M = p.length, N = a.length; initskip(p); for (i = M-1; i < N; i += Math.max(skip[a[i]], M-j)) { for (j = M-1, k = i; j >= 0 && a[k] == p[j]; j--) k--; if (j == -1) return k+1; } return N; } Forsimpling af Boyer-Moore's algoritme. Kun de tegn i a, som sammenlignes med p[M-1], benyttes til at springe fremad i a.

41 41 Syntaksanalyse Mål: Et program til indlæsning og beregning af aritmetiske udtryk (AE). Løs et lettere problem først: Læs en streng og undersøg, om den er et lovligt AE.

42 42 Benyt en kontekstfri grammatik til at beskrive AE: ::= | + | - ::= | * | / ::= | ( ) Grammatik for aritmetiske udtryk Grammatikken er beskrevet ved produktionsregler og består af (1) nonterminale symboler: expression, term, factor og number. (2) terminale symboler: +, -, *, /, ( og ). (3) metasymboler: ::=,, og |.

43 43 En streng er et aritmetisk udtryk, hvis det ved hjælp af produktionsreglerne er muligt at udlede strengen ud fra expression, dvs. ud fra expression i en række skridt nå frem til strengen ved i hvert skridt at erstatte et nonterminal-symbol med et af alternativerne på højresiden af en produktion for dette symbol. Syntaksanalyse Syntakstræ for (3*5+4/2)-1 expression term - expression factor term ( expression ) factor term + expression1 3 * 5 term 4 / 2

44 44 Syntaksdiagrammer expression: term + - term: factor * / factor: number expression ( )

45 45 Et rekursivt Java-program til syntaksanalyse kan konstrueres direkte ud fra syntakstræerne. Syntaksanalyse ved rekursiv nedstigning (top-down parsing) void expression() { term(); while (token == PLUS || token == MINUS) if (token == PLUS) { getToken(); term(); } else { getToken(); term(); } } static final int PLUS = 1, MINUS = 2, MULT = 3, DIV = 4, LPAR = 5, RPAR = 6, NUMBER = 7, EOS = 8; int token;

46 46 void factor() { if (token == NUMBER) ; else if (token == LPAR) { getToken(); expression(); if (token != RPAR) error("missing right paranthesis"); } else error("illegal factor: " + token); getToken(); } void term() { factor(); while (token == MULT || token == DIV) if (token == MULT) { getToken(); factor(); } else { getToken(); factor(); } }

47 47 StringTokenizer str; void parse(String s) { str = new StringTokenizer(s,"+-*/() ",true); getToken(); expression(); } Eksempel på kald: parse("(3*5+4/2)-1");

48 48 void getToken() { String s; try { s = str.nextToken(); } catch(NoSuchElementException e) { token = EOS; return; } if (s.equals(" ")) getToken(); else if (s.equals("+")) token = PLUS; else if (s.equals("-")) token = MINUS; else if (s.equals("*")) token = MULT; else if (s.equals("/")) token = DIV; else if (s.equals("(")) token = LPAR; else if (s.equals(")")) token = RPAR; else { try { Float.valueOf(s); token = NUMBER; } catch(NumberFormatException e) { error(”number expected"); }

49 49 Beregning af aritmetiske udtryk Beregning kan opnås ved få simple ændringer af syntaksanalyse-programmet. Analysemetoderne skal returnere med deres tilhørende værdi (i stedet for void ). float valueOf(String s) { str = new StringTokenizer(s,"+-*/() ",true); getToken(); return expression(); } Eksempel på kald: float r = valueOf("(3*5+4/2)-1");

50 50 float term() { float v = factor(); while (token == MULT || token == DIV) if (token == MULT) { getToken(); v *= factor(); } else { getToken(); v /= factor(); } return v; } float expression() { float v = term(); while (token == PLUS || token == MINUS) if (token == PLUS) { getToken(); v += term(); } else { getToken(); v -= term(); } return v; }

51 51 float factor() { float v; if (token == NUMBER) v = value; else if (token == LPAR) { getToken(); v = expression(); if (token != RPAR) error("missing right paranthesis"); } else error("illegal factor: " + token); getToken(); return v; }

52 52 void getToken() { String s; try { s = str.nextToken(); } catch(NoSuchElementException e) { token = EOS; return; } if (s.equals(" ")) getToken(); else if (s.equals("+")) token = PLUS; else if (s.equals("-")) token = MINUS; else if (s.equals("*")) token = MULT; else if (s.equals("/")) token = DIV; else if (s.equals("(")) token = LPAR; else if (s.equals(")")) token = RPAR; else { try{ value = Float.valueOf(s).getFloat(); token = NUMBER; } catch(NumberFormatException e) { error(”number expected"); }

53 53 Ugeseddel 8 4. november - 10. november Læs kapitel 29 og 30 i lærebogen (side 415-449) Løs følgende opgaver 1. Opgave 19.3, 19.4 og 19.6. 2. Opgave 21.1 og 21.2..


Download ppt "1 Strengbehandling. 2 Plan Strengsøgning - Knuth-Morris-Pratt-algoritmen endelige tilstandsmaskiner - Boyer-Moore-algoritmen - Rabin-Karp-algoritmen Syntaksanalyse."

Lignende præsentationer


Annoncer fra Google