1 /*************************************************************************
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
5 * Copyright 2000, 2010 Oracle and/or its affiliates.
7 * OpenOffice.org - a multi-platform office productivity suite
9 * This file is part of OpenOffice.org.
11 * OpenOffice.org is free software: you can redistribute it and/or modify
12 * it under the terms of the GNU Lesser General Public License version 3
13 * only, as published by the Free Software Foundation.
15 * OpenOffice.org is distributed in the hope that it will be useful,
16 * but WITHOUT ANY WARRANTY; without even the implied warranty of
17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 * GNU Lesser General Public License version 3 for more details
19 * (a copy is included in the LICENSE file that accompanied this code).
21 * You should have received a copy of the GNU Lesser General Public License
22 * version 3 along with OpenOffice.org. If not, see
23 * <http://www.openoffice.org/license.html>
24 * for a copy of the LGPLv3 License.
26 ************************************************************************/
28 // MARKER(update_precomp.py): autogen include statement, do not remove
29 #include "precompiled_i18npool.hxx"
30 /*************************************************************************
32 Weighted Levenshtein Distance
34 '*' for any number (0 or more) of arbitrary characters
35 '?' for exactly one arbitrary character
36 escapeable with backslash, "\*" or "\?"
39 WLD if WLD <= nLimit, else nLimit+1
43 -WLD if Replace and Insert and Delete <= nLimit
46 Recursive definition of WLD:
48 WLD( X(i), Y(j) ) = min( WLD( X(i-1), Y(j-1) ) + p(i,j) ,
49 WLD( X(i) , Y(j-1) ) + q ,
50 WLD( X(i-1), Y(j) ) + r )
52 X(i) := the first i characters of the word X
53 Y(j) := the first j characters of the word Y
54 p(i,j) := 0 if i-th character of X == j-th character of Y,
58 WLD( X(0), Y(j) ) := j*q (Y created by j inserts)
59 WLD( X(i), Y(0) ) := i*r (Y created by i deletes)
60 WLD( X(0), Y(0) ) := 0
62 Instead of recursions a dynamic algorithm is used.
64 See also: German computer magazine
65 c't 07/89 pages 192-208 and c't 03/94 pages 230-239
67 *************************************************************************/
70 #include <string.h> // strlen()
72 #if defined( _MSC_VER )
73 #pragma warning(once: 4068)
89 #define LEVDISBIG (nLimit + 1) // Returnwert wenn Distanz > nLimit
90 #define LEVDISDOUBLEBUF 2048 // dadrueber wird nicht mehr gedoppelt
92 // Balance, aus Geschwindigkeitsgruenden ist dieses keine Funktion
93 // c == cpPattern[jj] == cString[ii]
94 // erst wird bis Fundstelle gesucht, wenn dort die Balance gleich ist, wird
95 // auch nach der Fundstelle verglichen
96 #define LEVDISBALANCE(jj,ii) \
100 register sal_Int32 k; \
102 for ( k=0; k < jj; k++ ) \
103 if ( cpPattern[k] == c ) \
106 for ( k=0; k < ii; k++ ) \
107 if ( cString[k] == c ) \
111 for ( k=jj+1; k < nPatternLen; k++ ) \
112 if ( cpPattern[k] == c ) \
114 for ( k=ii+1; k < nStringLen; k++ ) \
115 if ( cString[k] == c ) \
121 static sal_Int32
Impl_WLD_StringLen( const sal_Unicode
* pStr
)
123 const sal_Unicode
* pTempStr
= pStr
;
126 return (sal_Int32
)(pTempStr
-pStr
);
130 #define erTESTMATMAX 180
131 static int npMatrix
[erTESTMATMAX
][erTESTMATMAX
]; // nearly 64K
134 // Distanz von String zu Pattern
135 int WLevDistance::WLD( const sal_Unicode
* cString
, sal_Int32 nStringLen
)
137 int nSPMin
= 0; // StrafPunkteMinimum
138 int nRepS
= 0; // fuer SplitCount
142 for ( sal_Int32 r
=0; r
<=nStringLen
&& r
< erTESTMATMAX
; r
++ )
143 for ( sal_Int32 c
=0; c
<=nPatternLen
&& c
< erTESTMATMAX
; c
++ )
144 npMatrix
[r
][c
] = 99; // Matrix initialisieren, nur visuell
148 // Laengendifferenz von Pattern und String
149 int nLenDiff
= nPatternLen
- nStars
- nStringLen
;
150 // mehr Einfuegungen oder Loeschungen noetig als Limit? => raus hier
151 if ( (nLenDiff
* nInsQ0
> nLimit
)
152 || ((nStars
== 0) && (nLenDiff
* nDelR0
< -nLimit
)) )
155 // wenn der zu vergleichende String groesser ist als das bisherige Array
156 // muss dieses angepasst werden
157 if ( nStringLen
>= nArrayLen
)
159 // gib ihm moeglichst mehr, damit nicht gleich naechstesmal
160 // wieder realloziert werden muss
161 if ( nStringLen
< LEVDISDOUBLEBUF
)
162 nArrayLen
= 2 * nStringLen
;
164 nArrayLen
= nStringLen
+ 1;
165 npDistance
= aDisMem
.NewMem( nArrayLen
);
169 cerr
<< "DOOM! (Damned, Out Of Memory)" << endl
;
175 // Anfangswerte der zweiten Spalte (erstes Pattern-Zeichen) berechnen
176 // die erste Spalte (0-Len Pattern) ist immer 0 .. nStringLen * nInsQ0,
177 // deren Minimum also 0
178 if ( nPatternLen
== 0 )
180 // Anzahl der Loeschungen, um auf Pattern zu kommen
181 for ( sal_Int32 i
=0; i
<= nStringLen
; i
++ )
182 npDistance
[i
] = i
* nDelR0
;
184 else if ( cpPattern
[0] == '*' && bpPatIsWild
[0] )
186 // statt einem '*' ist alles einsetzbar
187 for ( sal_Int32 i
=0; i
<= nStringLen
; i
++ )
195 if ( c
== '?' && bpPatIsWild
[0] )
196 nP
= 0; // ein '?' kann jedes Zeichen sein
198 // Minimum von Ersetzen und Loeschen+Einfuegen Gewichtung
199 nP
= Min3( nRepP0
, nRepP0
, nDelR0
+ nInsQ0
);
200 npDistance
[0] = nInsQ0
; // mit einfachem Einfuegen geht's los
201 npDistance
[1] = nInsQ0
;
202 npDistance
[2] = nInsQ0
;
203 int nReplacePos
= -1; // tristate Flag
205 for ( sal_Int32 i
=1; i
<= nStringLen
; i
++, nDelCnt
+= nDelR0
)
207 if ( cString
[i
-1] == c
)
208 nP
= 0; // Replace ab dieser Stelle ist 0
209 // Loeschungen um auf Pattern zu kommen + Replace
210 npDistance
[i
] = nDelCnt
+ nP
;
213 if ( nReplacePos
< 0 && nP
)
214 { // diese Stelle wird ersetzt
218 npMatrix
[i
][1] = -npDistance
[i
];
221 else if ( nReplacePos
> 0 && !nP
)
223 int nBalance
= 0; // gleiche Anzahl c
224 LEVDISBALANCE( 0, i
-1 );
226 { // einer wurde ersetzt, der ein Insert war
229 npMatrix
[nReplacePos
][1] = npDistance
[nReplacePos
];
236 nSPMin
= Min3( npDistance
[0], npDistance
[1], npDistance
[2] );
240 for ( sal_Int32 r
=0; r
<=nStringLen
&& r
< erTESTMATMAX
; r
++ )
242 npMatrix
[r
][0] = r
* nInsQ0
;
243 if ( npMatrix
[r
][1] >= 0)
244 npMatrix
[r
][1] = npDistance
[r
];
249 // Distanzmatrix berechnen
250 sal_Int32 j
= 0; // fuer alle Spalten des Pattern, solange nicht Limit
251 while ( (j
< nPatternLen
-1)
252 && nSPMin
<= (bSplitCount
? 2 * nLimit
: nLimit
) )
255 int nP
, nQ
, nR
, nPij
, d1
, d2
;
259 if ( bpPatIsWild
[j
] ) // '*' oder '?' nicht escaped
260 nP
= 0; // kann ohne Strafpunkte ersetzt werden
263 if ( c
== '*' && bpPatIsWild
[j
] )
265 nQ
= 0; // Einfuegen und Loeschen ohne Strafpunkte
270 nQ
= nInsQ0
; // normale Gewichtung
274 // Anzahl Einfuegungen um von Null-String auf Pattern zu kommen erhoehen
275 npDistance
[0] = npDistance
[0] + nQ
;
276 nSPMin
= npDistance
[0];
277 int nReplacePos
= -1; // tristate Flag
278 // fuer jede Patternspalte den String durchgehen
279 for ( register sal_Int32 i
=1; i
<= nStringLen
; i
++ )
281 d1
= d2
; // WLD( X(i-1), Y(j-1) )
282 d2
= npDistance
[i
]; // WLD( X(i) , Y(j-1) )
283 if ( cString
[i
-1] == c
)
286 if ( nReplacePos
< 0 )
288 int nBalance
= 0; // gleiche Anzahl c
289 LEVDISBALANCE( j
, i
-1 );
291 nReplacePos
= 0; // keine Ersetzung mehr
296 // WLD( X(i), Y(j) ) = min( WLD( X(i-1), Y(j-1) ) + p(i,j) ,
297 // WLD( X(i) , Y(j-1) ) + q ,
298 // WLD( X(i-1), Y(j) ) + r )
299 npDistance
[i
] = Min3( d1
+ nPij
, d2
+ nQ
, npDistance
[i
-1] + nR
);
300 if ( npDistance
[i
] < nSPMin
)
301 nSPMin
= npDistance
[i
];
304 if ( nReplacePos
< 0 && nPij
&& npDistance
[i
] == d1
+ nPij
)
305 { // diese Stelle wird ersetzt
309 npMatrix
[i
][j
+1] = -npDistance
[i
];
312 else if ( nReplacePos
> 0 && !nPij
)
313 { // Zeichen in String und Pattern gleich.
314 // wenn ab hier die gleiche Anzahl dieses Zeichens
315 // sowohl in Pattern als auch in String ist, und vor
316 // dieser Stelle das Zeichen gleich oft vorkommt, war das
317 // Replace keins. Buchstabendreher werden hier erfasst
318 // und der ReplaceS zurueckgenommen, wodurch das doppelte
319 // Limit zum Tragen kommt.
320 int nBalance
= 0; // gleiche Anzahl c
321 LEVDISBALANCE( j
, i
-1 );
323 { // einer wurde ersetzt, der ein Insert war
326 npMatrix
[nReplacePos
][j
+1] = npDistance
[nReplacePos
];
335 for ( sal_Int32 r
=0; r
<=nStringLen
&& r
< erTESTMATMAX
; r
++ )
336 if ( npMatrix
[r
][j
+1] >= 0)
337 npMatrix
[r
][j
+1] = npDistance
[r
];
342 printf(" nRepS: %d ", nRepS
);
344 if ( (nSPMin
<= nLimit
) && (npDistance
[nStringLen
] <= nLimit
) )
345 return(npDistance
[nStringLen
]);
350 if ( nRepS
&& nLenDiff
> 0 )
351 nRepS
-= nLenDiff
; // Inserts wurden mitgezaehlt
353 printf(" nRepSdiff: %d ", nRepS
);
355 if ( (nSPMin
<= 2 * nLimit
)
356 && (npDistance
[nStringLen
] <= 2 * nLimit
)
357 && (nRepS
* nRepP0
<= nLimit
) )
358 return( -npDistance
[nStringLen
] );
367 // Berechnung von nLimit, nReplP0, nInsQ0, nDelR0, bSplitCount
368 // aus Userwerten nOtherX, nShorterY, nLongerZ, bRelaxed
369 int WLevDistance::CalcLPQR( int nX
, int nY
, int nZ
, bool bRelaxed
)
371 int nMin
, nMid
, nMax
;
372 if ( nX
< 0 ) nX
= 0; // nur positive Werte
373 if ( nY
< 0 ) nY
= 0;
374 if ( nZ
< 0 ) nZ
= 0;
375 if ( 0 == (nMin
= Min3( nX
, nY
, nZ
)) ) // mindestens einer 0
377 nMax
= Max3( nX
, nY
, nZ
); // entweder 0 bei drei 0 oder Max
378 if ( 0 == (nMid
= Mid3( nX
, nY
, nZ
)) ) // sogar zwei 0
379 nLimit
= nMax
; // entweder 0 oder einziger >0
381 nLimit
= KGV( nMid
, nMax
);
383 else // alle drei nicht 0
384 nLimit
= KGV( KGV( nX
, nY
), nZ
);
385 nRepP0
= ( nX
? nLimit
/ nX
: nLimit
+ 1 );
386 nInsQ0
= ( nY
? nLimit
/ nY
: nLimit
+ 1 );
387 nDelR0
= ( nZ
? nLimit
/ nZ
: nLimit
+ 1 );
388 bSplitCount
= bRelaxed
;
394 // Groesster Gemeinsamer Teiler nach Euklid (Kettendivision)
395 // Sonderfall: 0 und irgendwas geben 1
396 int WLevDistance::GGT( int a
, int b
)
414 // Kleinstes Gemeinsames Vielfaches: a * b / GGT(a,b)
415 int WLevDistance::KGV( int a
, int b
)
417 if ( a
> b
) // Ueberlauf unwahrscheinlicher machen
418 return( (a
/ GGT(a
,b
)) * b
);
420 return( (b
/ GGT(a
,b
)) * a
);
424 // Minimum von drei Werten
425 inline int WLevDistance::Min3( int x
, int y
, int z
)
428 return( x
< z
? x
: z
);
430 return( y
< z
? y
: z
);
435 // mittlerer von drei Werten
436 int WLevDistance::Mid3( int x
, int y
, int z
)
438 int min
= Min3(x
,y
,z
);
440 return( y
< z
? y
: z
);
442 return( x
< z
? x
: z
);
444 return( x
< y
? x
: y
);
449 // Maximum von drei Werten
450 int WLevDistance::Max3( int x
, int y
, int z
)
453 return( x
> z
? x
: z
);
455 return( y
> z
? y
: z
);
460 // Daten aus CTor initialisieren
461 void WLevDistance::InitData( const sal_Unicode
* cPattern
)
463 cpPattern
= aPatMem
.GetcPtr();
464 bpPatIsWild
= aPatMem
.GetbPtr();
465 npDistance
= aDisMem
.GetPtr();
467 const sal_Unicode
* cp1
= cPattern
;
468 sal_Unicode
* cp2
= cpPattern
;
469 bool* bp
= bpPatIsWild
;
470 // Pattern kopieren, Sternchen zaehlen, escaped Jokers
473 if ( *cp1
== '\\' ) // maybe escaped
475 if ( *(cp1
+1) == '*' || *(cp1
+1) == '?' ) // naechstes Joker?
482 else if ( *cp1
== '*' || *cp1
== '?' ) // Joker
485 nStars
++; // Sternchenzaehler erhoehen
500 WLevDistance::WLevDistance( const ::rtl::OUString
& rPattern
) :
501 nPatternLen( rPattern
.getLength() ),
502 aPatMem( nPatternLen
+ 1 ),
503 nArrayLen( nPatternLen
+ 1 ),
504 aDisMem( nArrayLen
),
505 nLimit( LEVDISDEFAULTLIMIT
),
506 nRepP0( LEVDISDEFAULT_P0
),
507 nInsQ0( LEVDISDEFAULT_Q0
),
508 nDelR0( LEVDISDEFAULT_R0
),
511 InitData( rPattern
.getStr() );
517 WLevDistance::WLevDistance( const sal_Unicode
* cPattern
,
518 int nOtherX
, int nShorterY
, int nLongerZ
,
520 nPatternLen( Impl_WLD_StringLen(cPattern
) ),
521 aPatMem( nPatternLen
+ 1 ),
522 nArrayLen( nPatternLen
+ 1 ),
525 InitData( cPattern
);
526 CalcLPQR( nOtherX
, nShorterY
, nLongerZ
, bRelaxed
);
531 WLevDistance::WLevDistance( const WLevDistance
& rWLD
) :
532 nPatternLen( rWLD
.nPatternLen
),
533 aPatMem( nPatternLen
+ 1 ),
534 nArrayLen( nPatternLen
+ 1 ),
535 aDisMem( nArrayLen
),
536 nLimit( rWLD
.nLimit
),
537 nRepP0( rWLD
.nRepP0
),
538 nInsQ0( rWLD
.nInsQ0
),
539 nDelR0( rWLD
.nDelR0
),
540 nStars( rWLD
.nStars
),
541 bSplitCount( rWLD
.bSplitCount
)
543 cpPattern
= aPatMem
.GetcPtr();
544 bpPatIsWild
= aPatMem
.GetbPtr();
545 npDistance
= aDisMem
.GetPtr();
547 for ( i
=0; i
<nPatternLen
; i
++ )
549 cpPattern
[i
] = rWLD
.cpPattern
[i
];
550 bpPatIsWild
[i
] = rWLD
.bpPatIsWild
[i
];
557 WLevDistance::~WLevDistance()
561 /*************************************************************************
563 *************************************************************************/
567 #define LINESIZE 1000
568 typedef char MAXSTRING
[LINESIZE
+1];
572 void WLevDistance::ShowMatrix( const sal_Unicode
* cString
)
574 sal_Int32 r
, c
, l
= Impl_WLD_StringLen(cString
);
576 for ( c
=0; c
<nPatternLen
; c
++ )
577 #error Error: conversion from sal_Unicode to char needed!
578 printf( " %c ", cpPattern
[c
] );
580 for ( c
=0; c
<nPatternLen
; c
++ )
582 for ( r
=0; r
<=l
&& r
< erTESTMATMAX
; r
++ )
584 #error Error: conversion from sal_Unicode to char needed!
585 printf( "\n %c |", ( r
==0 ? ' ' : cString
[r
-1] ) );
586 for ( c
=0; c
<=nPatternLen
&& c
< erTESTMATMAX
; c
++ )
587 printf( "%2d ", npMatrix
[r
][c
] );
594 // Delimiter fuer Token, \t Tab bleibt fuer immer an der ersten Stelle
595 MAXSTRING cDelim
= "\t, ;(){}[]<>&=+-/%!|.\\'\"~";
597 void WLevDistance::ShowTest()
600 #error Error: conversion from sal_Unicode to char needed!
601 printf(" a *cpPattern . . . . : %s\n", cpPattern
);
602 printf(" b *bpPatIsWild . . . : ");
603 for ( sal_Int32 i
=0; i
<nPatternLen
; i
++ )
604 printf("%d", bpPatIsWild
[i
]);
606 printf(" c nPatternLen . . . : %d\n", (int)nPatternLen
);
607 printf(" d nStars . . . . . . : %d\n", nStars
);
608 printf(" e nLimit . . . . . . : %d\n", nLimit
);
609 printf(" f nRepP0 (Ersetzen) : %d\n", nRepP0
);
610 printf(" g nInsQ0 (Einfuegen) : %d\n", nInsQ0
);
611 printf(" h nDelR0 (Loeschen) : %d\n", nDelR0
);
612 printf(" i bSplitCount . . . : %d\n", bSplitCount
);
613 #error Error: conversion from sal_Unicode to char needed!
614 printf(" j cDelim . . . . . . : '%s'\n", cDelim
);
618 inline bool IsDelim( char c
)
627 MAXSTRING cString
, cLine
, cIgString
;
629 int main( int argc
, char **argv
)
631 int nLim
, nP0
, nQ0
, nR0
, nX
, nY
, nZ
;
633 bool IgnoreCase
= false, Direct
= false, bStrict
= false;
637 printf("%s Syntax:\n"
638 " ... [-i] cPattern [nOtherX, nShorterY, nLongerZ [bStrict [cDelim]]] [<stdin] [>stdout]\n"
639 " ... -d cPattern [nLimit [nRepP0 nInsQ0 nDelR0 [cDelim]]] [<stdin] [>stdout]\n"
643 if ( *argv
[1] == '-' )
647 switch ( *(argv
[1]+1) )
652 char* cp
= argv
[args
+1];
653 while ( (*cp
= tolower( *cp
)) != 0 )
665 nLim
= atoi(argv
[args
+2]);
667 nLim
= LEVDISDEFAULTLIMIT
;
670 nP0
= atoi(argv
[args
+3]);
671 nQ0
= atoi(argv
[args
+4]);
672 nR0
= atoi(argv
[args
+5]);
676 nP0
= LEVDISDEFAULT_P0
;
677 nQ0
= LEVDISDEFAULT_Q0
;
678 nR0
= LEVDISDEFAULT_R0
;
682 strncpy( cDelim
+1, argv
[args
+6], LINESIZE
); // \t Tab always remains
683 cDelim
[LINESIZE
-1] = 0;
690 nX
= atoi(argv
[args
+2]);
691 nY
= atoi(argv
[args
+3]);
692 nZ
= atoi(argv
[args
+4]);
696 nX
= LEVDISDEFAULT_XOTHER
;
697 nY
= LEVDISDEFAULT_YSHORTER
;
698 nZ
= LEVDISDEFAULT_ZLONGER
;
701 bStrict
= atoi(argv
[args
+5]);
704 strncpy( cDelim
+1, argv
[args
+6], LINESIZE
); // \t Tab always remains
705 cDelim
[LINESIZE
-1] = 0;
710 #error Error: conversion from char to OUString needed!
711 pTest
= new WLevDistance( argv
[args
+1] );
715 pTest
->SetLimit( nLim
);
716 pTest
->SetReplaceP0( nP0
);
717 pTest
->SetInsertQ0( nQ0
);
718 pTest
->SetDeleteR0( nR0
);
722 #error Error: conversion from char to sal_Unicode needed!
723 pTest
= new WLevDistance( argv
[args
+1], nX
, nY
, nZ
, !bStrict
);
725 WLevDistance
aTmp( *pTest
);
728 nLim
= pTest
->GetLimit();
734 static long unsigned int nLine
= 0;
736 cin
.getline( cLine
, LINESIZE
) ;
740 while ( *cp1
&& IsDelim(*cp1
) )
743 while ( *cp1
&& !IsDelim(*cp1
) )
746 while ( *cp1
&& IsDelim(*cp1
) )
753 char* cpi1
= cString
;
754 char* cpi2
= cIgString
;
756 *cpi2
++ = tolower( *cpi1
++ );
758 #error Error: conversion from char to OUString / sal_Unicode,length needed!
759 ret
= pTest
->WLD( cIgString
);
762 #error Error: conversion from char to OUString / sal_Unicode,length needed!
763 ret
= pTest
->WLD( cString
);
765 printf("\n# %3d : %s\n", ret
, cString
);
766 #error Error: conversion from char to sal_Unicode needed!
767 pTest
->ShowMatrix( cString
);
770 printf("# %3d : %s\t(line %lu)\t%s\n", ret
, cString
, nLine
, cLine
);
774 } while ( !cin
.eof() );