1 /*************************************************************************
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
5 * Copyright 2008 by Sun Microsystems, Inc.
7 * OpenOffice.org - a multi-platform office productivity suite
9 * $RCSfile: levdis.cxx,v $
10 * $Revision: 1.6.24.1 $
12 * This file is part of OpenOffice.org.
14 * OpenOffice.org is free software: you can redistribute it and/or modify
15 * it under the terms of the GNU Lesser General Public License version 3
16 * only, as published by the Free Software Foundation.
18 * OpenOffice.org is distributed in the hope that it will be useful,
19 * but WITHOUT ANY WARRANTY; without even the implied warranty of
20 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 * GNU Lesser General Public License version 3 for more details
22 * (a copy is included in the LICENSE file that accompanied this code).
24 * You should have received a copy of the GNU Lesser General Public License
25 * version 3 along with OpenOffice.org. If not, see
26 * <http://www.openoffice.org/license.html>
27 * for a copy of the LGPLv3 License.
29 ************************************************************************/
31 // MARKER(update_precomp.py): autogen include statement, do not remove
32 #include "precompiled_i18npool.hxx"
33 /*************************************************************************
35 Weighted Levenshtein Distance
37 '*' for any number (0 or more) of arbitrary characters
38 '?' for exactly one arbitrary character
39 escapeable with backslash, "\*" or "\?"
42 WLD if WLD <= nLimit, else nLimit+1
46 -WLD if Replace and Insert and Delete <= nLimit
49 Recursive definition of WLD:
51 WLD( X(i), Y(j) ) = min( WLD( X(i-1), Y(j-1) ) + p(i,j) ,
52 WLD( X(i) , Y(j-1) ) + q ,
53 WLD( X(i-1), Y(j) ) + r )
55 X(i) := the first i characters of the word X
56 Y(j) := the first j characters of the word Y
57 p(i,j) := 0 if i-th character of X == j-th character of Y,
61 WLD( X(0), Y(j) ) := j*q (Y created by j inserts)
62 WLD( X(i), Y(0) ) := i*r (Y created by i deletes)
63 WLD( X(0), Y(0) ) := 0
65 Instead of recursions a dynamic algorithm is used.
67 See also: German computer magazine
68 c't 07/89 pages 192-208 and c't 03/94 pages 230-239
70 *************************************************************************/
73 #include <string.h> // strlen()
75 #if defined( _MSC_VER )
76 #pragma warning(once: 4068)
92 #define LEVDISBIG (nLimit + 1) // Returnwert wenn Distanz > nLimit
93 #define LEVDISDOUBLEBUF 2048 // dadrueber wird nicht mehr gedoppelt
95 // Balance, aus Geschwindigkeitsgruenden ist dieses keine Funktion
96 // c == cpPattern[jj] == cString[ii]
97 // erst wird bis Fundstelle gesucht, wenn dort die Balance gleich ist, wird
98 // auch nach der Fundstelle verglichen
99 #define LEVDISBALANCE(jj,ii) \
103 register sal_Int32 k; \
105 for ( k=0; k < jj; k++ ) \
106 if ( cpPattern[k] == c ) \
109 for ( k=0; k < ii; k++ ) \
110 if ( cString[k] == c ) \
114 for ( k=jj+1; k < nPatternLen; k++ ) \
115 if ( cpPattern[k] == c ) \
117 for ( k=ii+1; k < nStringLen; k++ ) \
118 if ( cString[k] == c ) \
124 static sal_Int32
Impl_WLD_StringLen( const sal_Unicode
* pStr
)
126 const sal_Unicode
* pTempStr
= pStr
;
129 return (sal_Int32
)(pTempStr
-pStr
);
133 #define erTESTMATMAX 180
134 static int npMatrix
[erTESTMATMAX
][erTESTMATMAX
]; // nearly 64K
137 // Distanz von String zu Pattern
138 int WLevDistance::WLD( const sal_Unicode
* cString
, sal_Int32 nStringLen
)
140 int nSPMin
= 0; // StrafPunkteMinimum
141 int nRepS
= 0; // fuer SplitCount
145 for ( sal_Int32 r
=0; r
<=nStringLen
&& r
< erTESTMATMAX
; r
++ )
146 for ( sal_Int32 c
=0; c
<=nPatternLen
&& c
< erTESTMATMAX
; c
++ )
147 npMatrix
[r
][c
] = 99; // Matrix initialisieren, nur visuell
151 // Laengendifferenz von Pattern und String
152 int nLenDiff
= nPatternLen
- nStars
- nStringLen
;
153 // mehr Einfuegungen oder Loeschungen noetig als Limit? => raus hier
154 if ( (nLenDiff
* nInsQ0
> nLimit
)
155 || ((nStars
== 0) && (nLenDiff
* nDelR0
< -nLimit
)) )
158 // wenn der zu vergleichende String groesser ist als das bisherige Array
159 // muss dieses angepasst werden
160 if ( nStringLen
>= nArrayLen
)
162 // gib ihm moeglichst mehr, damit nicht gleich naechstesmal
163 // wieder realloziert werden muss
164 if ( nStringLen
< LEVDISDOUBLEBUF
)
165 nArrayLen
= 2 * nStringLen
;
167 nArrayLen
= nStringLen
+ 1;
168 npDistance
= aDisMem
.NewMem( nArrayLen
);
172 cerr
<< "DOOM! (Damned, Out Of Memory)" << endl
;
178 // Anfangswerte der zweiten Spalte (erstes Pattern-Zeichen) berechnen
179 // die erste Spalte (0-Len Pattern) ist immer 0 .. nStringLen * nInsQ0,
180 // deren Minimum also 0
181 if ( nPatternLen
== 0 )
183 // Anzahl der Loeschungen, um auf Pattern zu kommen
184 for ( sal_Int32 i
=0; i
<= nStringLen
; i
++ )
185 npDistance
[i
] = i
* nDelR0
;
187 else if ( cpPattern
[0] == '*' && bpPatIsWild
[0] )
189 // statt einem '*' ist alles einsetzbar
190 for ( sal_Int32 i
=0; i
<= nStringLen
; i
++ )
198 if ( c
== '?' && bpPatIsWild
[0] )
199 nP
= 0; // ein '?' kann jedes Zeichen sein
201 // Minimum von Ersetzen und Loeschen+Einfuegen Gewichtung
202 nP
= Min3( nRepP0
, nRepP0
, nDelR0
+ nInsQ0
);
203 npDistance
[0] = nInsQ0
; // mit einfachem Einfuegen geht's los
204 npDistance
[1] = nInsQ0
;
205 npDistance
[2] = nInsQ0
;
206 int nReplacePos
= -1; // tristate Flag
208 for ( sal_Int32 i
=1; i
<= nStringLen
; i
++, nDelCnt
+= nDelR0
)
210 if ( cString
[i
-1] == c
)
211 nP
= 0; // Replace ab dieser Stelle ist 0
212 // Loeschungen um auf Pattern zu kommen + Replace
213 npDistance
[i
] = nDelCnt
+ nP
;
216 if ( nReplacePos
< 0 && nP
)
217 { // diese Stelle wird ersetzt
221 npMatrix
[i
][1] = -npDistance
[i
];
224 else if ( nReplacePos
> 0 && !nP
)
226 int nBalance
= 0; // gleiche Anzahl c
227 LEVDISBALANCE( 0, i
-1 );
229 { // einer wurde ersetzt, der ein Insert war
232 npMatrix
[nReplacePos
][1] = npDistance
[nReplacePos
];
239 nSPMin
= Min3( npDistance
[0], npDistance
[1], npDistance
[2] );
243 for ( sal_Int32 r
=0; r
<=nStringLen
&& r
< erTESTMATMAX
; r
++ )
245 npMatrix
[r
][0] = r
* nInsQ0
;
246 if ( npMatrix
[r
][1] >= 0)
247 npMatrix
[r
][1] = npDistance
[r
];
252 // Distanzmatrix berechnen
253 sal_Int32 j
= 0; // fuer alle Spalten des Pattern, solange nicht Limit
254 while ( (j
< nPatternLen
-1)
255 && nSPMin
<= (bSplitCount
? 2 * nLimit
: nLimit
) )
258 int nP
, nQ
, nR
, nPij
, d1
, d2
;
262 if ( bpPatIsWild
[j
] ) // '*' oder '?' nicht escaped
263 nP
= 0; // kann ohne Strafpunkte ersetzt werden
266 if ( c
== '*' && bpPatIsWild
[j
] )
268 nQ
= 0; // Einfuegen und Loeschen ohne Strafpunkte
273 nQ
= nInsQ0
; // normale Gewichtung
277 // Anzahl Einfuegungen um von Null-String auf Pattern zu kommen erhoehen
278 npDistance
[0] = npDistance
[0] + nQ
;
279 nSPMin
= npDistance
[0];
280 int nReplacePos
= -1; // tristate Flag
281 // fuer jede Patternspalte den String durchgehen
282 for ( register sal_Int32 i
=1; i
<= nStringLen
; i
++ )
284 d1
= d2
; // WLD( X(i-1), Y(j-1) )
285 d2
= npDistance
[i
]; // WLD( X(i) , Y(j-1) )
286 if ( cString
[i
-1] == c
)
289 if ( nReplacePos
< 0 )
291 int nBalance
= 0; // gleiche Anzahl c
292 LEVDISBALANCE( j
, i
-1 );
294 nReplacePos
= 0; // keine Ersetzung mehr
299 // WLD( X(i), Y(j) ) = min( WLD( X(i-1), Y(j-1) ) + p(i,j) ,
300 // WLD( X(i) , Y(j-1) ) + q ,
301 // WLD( X(i-1), Y(j) ) + r )
302 npDistance
[i
] = Min3( d1
+ nPij
, d2
+ nQ
, npDistance
[i
-1] + nR
);
303 if ( npDistance
[i
] < nSPMin
)
304 nSPMin
= npDistance
[i
];
307 if ( nReplacePos
< 0 && nPij
&& npDistance
[i
] == d1
+ nPij
)
308 { // diese Stelle wird ersetzt
312 npMatrix
[i
][j
+1] = -npDistance
[i
];
315 else if ( nReplacePos
> 0 && !nPij
)
316 { // Zeichen in String und Pattern gleich.
317 // wenn ab hier die gleiche Anzahl dieses Zeichens
318 // sowohl in Pattern als auch in String ist, und vor
319 // dieser Stelle das Zeichen gleich oft vorkommt, war das
320 // Replace keins. Buchstabendreher werden hier erfasst
321 // und der ReplaceS zurueckgenommen, wodurch das doppelte
322 // Limit zum Tragen kommt.
323 int nBalance
= 0; // gleiche Anzahl c
324 LEVDISBALANCE( j
, i
-1 );
326 { // einer wurde ersetzt, der ein Insert war
329 npMatrix
[nReplacePos
][j
+1] = npDistance
[nReplacePos
];
338 for ( sal_Int32 r
=0; r
<=nStringLen
&& r
< erTESTMATMAX
; r
++ )
339 if ( npMatrix
[r
][j
+1] >= 0)
340 npMatrix
[r
][j
+1] = npDistance
[r
];
345 printf(" nRepS: %d ", nRepS
);
347 if ( (nSPMin
<= nLimit
) && (npDistance
[nStringLen
] <= nLimit
) )
348 return(npDistance
[nStringLen
]);
353 if ( nRepS
&& nLenDiff
> 0 )
354 nRepS
-= nLenDiff
; // Inserts wurden mitgezaehlt
356 printf(" nRepSdiff: %d ", nRepS
);
358 if ( (nSPMin
<= 2 * nLimit
)
359 && (npDistance
[nStringLen
] <= 2 * nLimit
)
360 && (nRepS
* nRepP0
<= nLimit
) )
361 return( -npDistance
[nStringLen
] );
370 // Berechnung von nLimit, nReplP0, nInsQ0, nDelR0, bSplitCount
371 // aus Userwerten nOtherX, nShorterY, nLongerZ, bRelaxed
372 int WLevDistance::CalcLPQR( int nX
, int nY
, int nZ
, bool bRelaxed
)
374 int nMin
, nMid
, nMax
;
375 if ( nX
< 0 ) nX
= 0; // nur positive Werte
376 if ( nY
< 0 ) nY
= 0;
377 if ( nZ
< 0 ) nZ
= 0;
378 if ( 0 == (nMin
= Min3( nX
, nY
, nZ
)) ) // mindestens einer 0
380 nMax
= Max3( nX
, nY
, nZ
); // entweder 0 bei drei 0 oder Max
381 if ( 0 == (nMid
= Mid3( nX
, nY
, nZ
)) ) // sogar zwei 0
382 nLimit
= nMax
; // entweder 0 oder einziger >0
384 nLimit
= KGV( nMid
, nMax
);
386 else // alle drei nicht 0
387 nLimit
= KGV( KGV( nX
, nY
), nZ
);
388 nRepP0
= ( nX
? nLimit
/ nX
: nLimit
+ 1 );
389 nInsQ0
= ( nY
? nLimit
/ nY
: nLimit
+ 1 );
390 nDelR0
= ( nZ
? nLimit
/ nZ
: nLimit
+ 1 );
391 bSplitCount
= bRelaxed
;
397 // Groesster Gemeinsamer Teiler nach Euklid (Kettendivision)
398 // Sonderfall: 0 und irgendwas geben 1
399 int WLevDistance::GGT( int a
, int b
)
417 // Kleinstes Gemeinsames Vielfaches: a * b / GGT(a,b)
418 int WLevDistance::KGV( int a
, int b
)
420 if ( a
> b
) // Ueberlauf unwahrscheinlicher machen
421 return( (a
/ GGT(a
,b
)) * b
);
423 return( (b
/ GGT(a
,b
)) * a
);
427 // Minimum von drei Werten
428 inline int WLevDistance::Min3( int x
, int y
, int z
)
431 return( x
< z
? x
: z
);
433 return( y
< z
? y
: z
);
438 // mittlerer von drei Werten
439 int WLevDistance::Mid3( int x
, int y
, int z
)
441 int min
= Min3(x
,y
,z
);
443 return( y
< z
? y
: z
);
445 return( x
< z
? x
: z
);
447 return( x
< y
? x
: y
);
452 // Maximum von drei Werten
453 int WLevDistance::Max3( int x
, int y
, int z
)
456 return( x
> z
? x
: z
);
458 return( y
> z
? y
: z
);
463 // Daten aus CTor initialisieren
464 void WLevDistance::InitData( const sal_Unicode
* cPattern
)
466 cpPattern
= aPatMem
.GetcPtr();
467 bpPatIsWild
= aPatMem
.GetbPtr();
468 npDistance
= aDisMem
.GetPtr();
470 const sal_Unicode
* cp1
= cPattern
;
471 sal_Unicode
* cp2
= cpPattern
;
472 bool* bp
= bpPatIsWild
;
473 // Pattern kopieren, Sternchen zaehlen, escaped Jokers
476 if ( *cp1
== '\\' ) // maybe escaped
478 if ( *(cp1
+1) == '*' || *(cp1
+1) == '?' ) // naechstes Joker?
485 else if ( *cp1
== '*' || *cp1
== '?' ) // Joker
488 nStars
++; // Sternchenzaehler erhoehen
503 WLevDistance::WLevDistance( const ::rtl::OUString
& rPattern
) :
504 nPatternLen( rPattern
.getLength() ),
505 aPatMem( nPatternLen
+ 1 ),
506 nArrayLen( nPatternLen
+ 1 ),
507 aDisMem( nArrayLen
),
508 nLimit( LEVDISDEFAULTLIMIT
),
509 nRepP0( LEVDISDEFAULT_P0
),
510 nInsQ0( LEVDISDEFAULT_Q0
),
511 nDelR0( LEVDISDEFAULT_R0
),
514 InitData( rPattern
.getStr() );
520 WLevDistance::WLevDistance( const sal_Unicode
* cPattern
,
521 int nOtherX
, int nShorterY
, int nLongerZ
,
523 nPatternLen( Impl_WLD_StringLen(cPattern
) ),
524 aPatMem( nPatternLen
+ 1 ),
525 nArrayLen( nPatternLen
+ 1 ),
528 InitData( cPattern
);
529 CalcLPQR( nOtherX
, nShorterY
, nLongerZ
, bRelaxed
);
534 WLevDistance::WLevDistance( const WLevDistance
& rWLD
) :
535 nPatternLen( rWLD
.nPatternLen
),
536 aPatMem( nPatternLen
+ 1 ),
537 nArrayLen( nPatternLen
+ 1 ),
538 aDisMem( nArrayLen
),
539 nLimit( rWLD
.nLimit
),
540 nRepP0( rWLD
.nRepP0
),
541 nInsQ0( rWLD
.nInsQ0
),
542 nDelR0( rWLD
.nDelR0
),
543 nStars( rWLD
.nStars
),
544 bSplitCount( rWLD
.bSplitCount
)
546 cpPattern
= aPatMem
.GetcPtr();
547 bpPatIsWild
= aPatMem
.GetbPtr();
548 npDistance
= aDisMem
.GetPtr();
550 for ( i
=0; i
<nPatternLen
; i
++ )
552 cpPattern
[i
] = rWLD
.cpPattern
[i
];
553 bpPatIsWild
[i
] = rWLD
.bpPatIsWild
[i
];
560 WLevDistance::~WLevDistance()
564 /*************************************************************************
566 *************************************************************************/
570 #define LINESIZE 1000
571 typedef char MAXSTRING
[LINESIZE
+1];
575 void WLevDistance::ShowMatrix( const sal_Unicode
* cString
)
577 sal_Int32 r
, c
, l
= Impl_WLD_StringLen(cString
);
579 for ( c
=0; c
<nPatternLen
; c
++ )
580 #error Error: conversion from sal_Unicode to char needed!
581 printf( " %c ", cpPattern
[c
] );
583 for ( c
=0; c
<nPatternLen
; c
++ )
585 for ( r
=0; r
<=l
&& r
< erTESTMATMAX
; r
++ )
587 #error Error: conversion from sal_Unicode to char needed!
588 printf( "\n %c |", ( r
==0 ? ' ' : cString
[r
-1] ) );
589 for ( c
=0; c
<=nPatternLen
&& c
< erTESTMATMAX
; c
++ )
590 printf( "%2d ", npMatrix
[r
][c
] );
597 // Delimiter fuer Token, \t Tab bleibt fuer immer an der ersten Stelle
598 MAXSTRING cDelim
= "\t, ;(){}[]<>&=+-/%!|.\\'\"~";
600 void WLevDistance::ShowTest()
603 #error Error: conversion from sal_Unicode to char needed!
604 printf(" a *cpPattern . . . . : %s\n", cpPattern
);
605 printf(" b *bpPatIsWild . . . : ");
606 for ( sal_Int32 i
=0; i
<nPatternLen
; i
++ )
607 printf("%d", bpPatIsWild
[i
]);
609 printf(" c nPatternLen . . . : %d\n", (int)nPatternLen
);
610 printf(" d nStars . . . . . . : %d\n", nStars
);
611 printf(" e nLimit . . . . . . : %d\n", nLimit
);
612 printf(" f nRepP0 (Ersetzen) : %d\n", nRepP0
);
613 printf(" g nInsQ0 (Einfuegen) : %d\n", nInsQ0
);
614 printf(" h nDelR0 (Loeschen) : %d\n", nDelR0
);
615 printf(" i bSplitCount . . . : %d\n", bSplitCount
);
616 #error Error: conversion from sal_Unicode to char needed!
617 printf(" j cDelim . . . . . . : '%s'\n", cDelim
);
621 inline bool IsDelim( char c
)
630 MAXSTRING cString
, cLine
, cIgString
;
632 int main( int argc
, char **argv
)
634 int nLim
, nP0
, nQ0
, nR0
, nX
, nY
, nZ
;
636 bool IgnoreCase
= false, Direct
= false, bStrict
= false;
640 printf("%s Syntax:\n"
641 " ... [-i] cPattern [nOtherX, nShorterY, nLongerZ [bStrict [cDelim]]] [<stdin] [>stdout]\n"
642 " ... -d cPattern [nLimit [nRepP0 nInsQ0 nDelR0 [cDelim]]] [<stdin] [>stdout]\n"
646 if ( *argv
[1] == '-' )
650 switch ( *(argv
[1]+1) )
655 char* cp
= argv
[args
+1];
656 while ( (*cp
= tolower( *cp
)) != 0 )
668 nLim
= atoi(argv
[args
+2]);
670 nLim
= LEVDISDEFAULTLIMIT
;
673 nP0
= atoi(argv
[args
+3]);
674 nQ0
= atoi(argv
[args
+4]);
675 nR0
= atoi(argv
[args
+5]);
679 nP0
= LEVDISDEFAULT_P0
;
680 nQ0
= LEVDISDEFAULT_Q0
;
681 nR0
= LEVDISDEFAULT_R0
;
685 strncpy( cDelim
+1, argv
[args
+6], LINESIZE
); // \t Tab always remains
686 cDelim
[LINESIZE
-1] = 0;
693 nX
= atoi(argv
[args
+2]);
694 nY
= atoi(argv
[args
+3]);
695 nZ
= atoi(argv
[args
+4]);
699 nX
= LEVDISDEFAULT_XOTHER
;
700 nY
= LEVDISDEFAULT_YSHORTER
;
701 nZ
= LEVDISDEFAULT_ZLONGER
;
704 bStrict
= atoi(argv
[args
+5]);
707 strncpy( cDelim
+1, argv
[args
+6], LINESIZE
); // \t Tab always remains
708 cDelim
[LINESIZE
-1] = 0;
713 #error Error: conversion from char to OUString needed!
714 pTest
= new WLevDistance( argv
[args
+1] );
718 pTest
->SetLimit( nLim
);
719 pTest
->SetReplaceP0( nP0
);
720 pTest
->SetInsertQ0( nQ0
);
721 pTest
->SetDeleteR0( nR0
);
725 #error Error: conversion from char to sal_Unicode needed!
726 pTest
= new WLevDistance( argv
[args
+1], nX
, nY
, nZ
, !bStrict
);
728 WLevDistance
aTmp( *pTest
);
731 nLim
= pTest
->GetLimit();
737 static long unsigned int nLine
= 0;
739 cin
.getline( cLine
, LINESIZE
) ;
743 while ( *cp1
&& IsDelim(*cp1
) )
746 while ( *cp1
&& !IsDelim(*cp1
) )
749 while ( *cp1
&& IsDelim(*cp1
) )
756 char* cpi1
= cString
;
757 char* cpi2
= cIgString
;
759 *cpi2
++ = tolower( *cpi1
++ );
761 #error Error: conversion from char to OUString / sal_Unicode,length needed!
762 ret
= pTest
->WLD( cIgString
);
765 #error Error: conversion from char to OUString / sal_Unicode,length needed!
766 ret
= pTest
->WLD( cString
);
768 printf("\n# %3d : %s\n", ret
, cString
);
769 #error Error: conversion from char to sal_Unicode needed!
770 pTest
->ShowMatrix( cString
);
773 printf("# %3d : %s\t(line %lu)\t%s\n", ret
, cString
, nLine
, cLine
);
777 } while ( !cin
.eof() );