1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
3 * This file is part of the LibreOffice project.
5 * This Source Code Form is subject to the terms of the Mozilla Public
6 * License, v. 2.0. If a copy of the MPL was not distributed with this
7 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
9 * This file incorporates work covered by the following license notice:
11 * Licensed to the Apache Software Foundation (ASF) under one or more
12 * contributor license agreements. See the NOTICE file distributed
13 * with this work for additional information regarding copyright
14 * ownership. The ASF licenses this file to you under the Apache
15 * License, Version 2.0 (the "License"); you may not use this file
16 * except in compliance with the License. You may obtain a copy of
17 * the License at http://www.apache.org/licenses/LICENSE-2.0 .
20 /*************************************************************************
22 Weighted Levenshtein Distance
24 '*' for any number (0 or more) of arbitrary characters
25 '?' for exactly one arbitrary character
26 escapeable with backslash, "\*" or "\?"
29 WLD if WLD <= nLimit, else nLimit+1
33 -WLD if Replace and Insert and Delete <= nLimit
36 Recursive definition of WLD:
38 WLD( X(i), Y(j) ) = min( WLD( X(i-1), Y(j-1) ) + p(i,j) ,
39 WLD( X(i) , Y(j-1) ) + q ,
40 WLD( X(i-1), Y(j) ) + r )
42 X(i) := the first i characters of the word X
43 Y(j) := the first j characters of the word Y
44 p(i,j) := 0 if i-th character of X == j-th character of Y,
48 WLD( X(0), Y(j) ) := j*q (Y created by j inserts)
49 WLD( X(i), Y(0) ) := i*r (Y created by i deletes)
50 WLD( X(0), Y(0) ) := 0
52 Instead of recursions a dynamic algorithm is used.
54 See also: German computer magazine
55 c't 07/89 pages 192-208 and c't 03/94 pages 230-239
57 *************************************************************************/
60 #include <string.h> // strlen()
62 #if defined( _MSC_VER )
63 #pragma warning(once: 4068)
72 #define LEVDISBIG (nLimit + 1) // Returnwert wenn Distanz > nLimit
73 #define LEVDISDOUBLEBUF 2048 // dadrueber wird nicht mehr gedoppelt
75 // Balance, aus Geschwindigkeitsgruenden ist dieses keine Funktion
76 // c == cpPattern[jj] == cString[ii]
77 // erst wird bis Fundstelle gesucht, wenn dort die Balance gleich ist, wird
78 // auch nach der Fundstelle verglichen
79 #define LEVDISBALANCE(jj,ii) \
83 register sal_Int32 k; \
85 for ( k=0; k < jj; k++ ) \
86 if ( cpPattern[k] == c ) \
89 for ( k=0; k < ii; k++ ) \
90 if ( cString[k] == c ) \
94 for ( k=jj+1; k < nPatternLen; k++ ) \
95 if ( cpPattern[k] == c ) \
97 for ( k=ii+1; k < nStringLen; k++ ) \
98 if ( cString[k] == c ) \
104 static sal_Int32
Impl_WLD_StringLen( const sal_Unicode
* pStr
)
106 const sal_Unicode
* pTempStr
= pStr
;
109 return (sal_Int32
)(pTempStr
-pStr
);
112 // Distanz von String zu Pattern
113 int WLevDistance::WLD( const sal_Unicode
* cString
, sal_Int32 nStringLen
)
115 int nSPMin
= 0; // StrafPunkteMinimum
116 int nRepS
= 0; // fuer SplitCount
118 // Laengendifferenz von Pattern und String
119 int nLenDiff
= nPatternLen
- nStars
- nStringLen
;
120 // mehr Einfuegungen oder Loeschungen noetig als Limit? => raus hier
121 if ( (nLenDiff
* nInsQ0
> nLimit
)
122 || ((nStars
== 0) && (nLenDiff
* nDelR0
< -nLimit
)) )
125 // wenn der zu vergleichende String groesser ist als das bisherige Array
126 // muss dieses angepasst werden
127 if ( nStringLen
>= nArrayLen
)
129 // gib ihm moeglichst mehr, damit nicht gleich naechstesmal
130 // wieder realloziert werden muss
131 if ( nStringLen
< LEVDISDOUBLEBUF
)
132 nArrayLen
= 2 * nStringLen
;
134 nArrayLen
= nStringLen
+ 1;
135 npDistance
= aDisMem
.NewMem( nArrayLen
);
138 // Anfangswerte der zweiten Spalte (erstes Pattern-Zeichen) berechnen
139 // die erste Spalte (0-Len Pattern) ist immer 0 .. nStringLen * nInsQ0,
140 // deren Minimum also 0
141 if ( nPatternLen
== 0 )
143 // Anzahl der Loeschungen, um auf Pattern zu kommen
144 for ( sal_Int32 i
=0; i
<= nStringLen
; i
++ )
145 npDistance
[i
] = i
* nDelR0
;
147 else if ( cpPattern
[0] == '*' && bpPatIsWild
[0] )
149 // statt einem '*' ist alles einsetzbar
150 for ( sal_Int32 i
=0; i
<= nStringLen
; i
++ )
158 if ( c
== '?' && bpPatIsWild
[0] )
159 nP
= 0; // ein '?' kann jedes Zeichen sein
161 // Minimum von Ersetzen und Loeschen+Einfuegen Gewichtung
162 nP
= Min3( nRepP0
, nRepP0
, nDelR0
+ nInsQ0
);
163 npDistance
[0] = nInsQ0
; // mit einfachem Einfuegen geht's los
164 npDistance
[1] = nInsQ0
;
165 npDistance
[2] = nInsQ0
;
166 int nReplacePos
= -1; // tristate Flag
168 for ( sal_Int32 i
=1; i
<= nStringLen
; i
++, nDelCnt
+= nDelR0
)
170 if ( cString
[i
-1] == c
)
171 nP
= 0; // Replace ab dieser Stelle ist 0
172 // Loeschungen um auf Pattern zu kommen + Replace
173 npDistance
[i
] = nDelCnt
+ nP
;
176 if ( nReplacePos
< 0 && nP
)
177 { // diese Stelle wird ersetzt
181 else if ( nReplacePos
> 0 && !nP
)
183 int nBalance
= 0; // gleiche Anzahl c
184 LEVDISBALANCE( 0, i
-1 );
186 { // einer wurde ersetzt, der ein Insert war
193 nSPMin
= Min3( npDistance
[0], npDistance
[1], npDistance
[2] );
196 // Distanzmatrix berechnen
197 sal_Int32 j
= 0; // fuer alle Spalten des Pattern, solange nicht Limit
198 while ( (j
< nPatternLen
-1)
199 && nSPMin
<= (bSplitCount
? 2 * nLimit
: nLimit
) )
202 int nP
, nQ
, nR
, nPij
, d1
, d2
;
206 if ( bpPatIsWild
[j
] ) // '*' oder '?' nicht escaped
207 nP
= 0; // kann ohne Strafpunkte ersetzt werden
210 if ( c
== '*' && bpPatIsWild
[j
] )
212 nQ
= 0; // Einfuegen und Loeschen ohne Strafpunkte
217 nQ
= nInsQ0
; // normale Gewichtung
221 // Anzahl Einfuegungen um von Null-String auf Pattern zu kommen erhoehen
222 npDistance
[0] = npDistance
[0] + nQ
;
223 nSPMin
= npDistance
[0];
224 int nReplacePos
= -1; // tristate Flag
225 // fuer jede Patternspalte den String durchgehen
226 for ( register sal_Int32 i
=1; i
<= nStringLen
; i
++ )
228 d1
= d2
; // WLD( X(i-1), Y(j-1) )
229 d2
= npDistance
[i
]; // WLD( X(i) , Y(j-1) )
230 if ( cString
[i
-1] == c
)
233 if ( nReplacePos
< 0 )
235 int nBalance
= 0; // gleiche Anzahl c
236 LEVDISBALANCE( j
, i
-1 );
238 nReplacePos
= 0; // keine Ersetzung mehr
243 // WLD( X(i), Y(j) ) = min( WLD( X(i-1), Y(j-1) ) + p(i,j) ,
244 // WLD( X(i) , Y(j-1) ) + q ,
245 // WLD( X(i-1), Y(j) ) + r )
246 npDistance
[i
] = Min3( d1
+ nPij
, d2
+ nQ
, npDistance
[i
-1] + nR
);
247 if ( npDistance
[i
] < nSPMin
)
248 nSPMin
= npDistance
[i
];
251 if ( nReplacePos
< 0 && nPij
&& npDistance
[i
] == d1
+ nPij
)
252 { // diese Stelle wird ersetzt
256 else if ( nReplacePos
> 0 && !nPij
)
257 { // Zeichen in String und Pattern gleich.
258 // wenn ab hier die gleiche Anzahl dieses Zeichens
259 // sowohl in Pattern als auch in String ist, und vor
260 // dieser Stelle das Zeichen gleich oft vorkommt, war das
261 // Replace keins. Buchstabendreher werden hier erfasst
262 // und der ReplaceS zurueckgenommen, wodurch das doppelte
263 // Limit zum Tragen kommt.
264 int nBalance
= 0; // gleiche Anzahl c
265 LEVDISBALANCE( j
, i
-1 );
267 { // einer wurde ersetzt, der ein Insert war
275 if ( (nSPMin
<= nLimit
) && (npDistance
[nStringLen
] <= nLimit
) )
276 return(npDistance
[nStringLen
]);
281 if ( nRepS
&& nLenDiff
> 0 )
282 nRepS
-= nLenDiff
; // Inserts wurden mitgezaehlt
283 if ( (nSPMin
<= 2 * nLimit
)
284 && (npDistance
[nStringLen
] <= 2 * nLimit
)
285 && (nRepS
* nRepP0
<= nLimit
) )
286 return( -npDistance
[nStringLen
] );
295 // Berechnung von nLimit, nReplP0, nInsQ0, nDelR0, bSplitCount
296 // aus Userwerten nOtherX, nShorterY, nLongerZ, bRelaxed
297 int WLevDistance::CalcLPQR( int nX
, int nY
, int nZ
, bool bRelaxed
)
299 if ( nX
< 0 ) nX
= 0; // nur positive Werte
300 if ( nY
< 0 ) nY
= 0;
301 if ( nZ
< 0 ) nZ
= 0;
302 if (0 == Min3( nX
, nY
, nZ
)) // mindestens einer 0
305 nMax
= Max3( nX
, nY
, nZ
); // entweder 0 bei drei 0 oder Max
306 if ( 0 == (nMid
= Mid3( nX
, nY
, nZ
)) ) // sogar zwei 0
307 nLimit
= nMax
; // entweder 0 oder einziger >0
309 nLimit
= KGV( nMid
, nMax
);
311 else // alle drei nicht 0
312 nLimit
= KGV( KGV( nX
, nY
), nZ
);
313 nRepP0
= ( nX
? nLimit
/ nX
: nLimit
+ 1 );
314 nInsQ0
= ( nY
? nLimit
/ nY
: nLimit
+ 1 );
315 nDelR0
= ( nZ
? nLimit
/ nZ
: nLimit
+ 1 );
316 bSplitCount
= bRelaxed
;
322 // Groesster Gemeinsamer Teiler nach Euklid (Kettendivision)
323 // Sonderfall: 0 und irgendwas geben 1
324 int WLevDistance::GGT( int a
, int b
)
342 // Kleinstes Gemeinsames Vielfaches: a * b / GGT(a,b)
343 int WLevDistance::KGV( int a
, int b
)
345 if ( a
> b
) // Ueberlauf unwahrscheinlicher machen
346 return( (a
/ GGT(a
,b
)) * b
);
348 return( (b
/ GGT(a
,b
)) * a
);
352 // Minimum von drei Werten
353 inline int WLevDistance::Min3( int x
, int y
, int z
)
356 return( x
< z
? x
: z
);
358 return( y
< z
? y
: z
);
363 // mittlerer von drei Werten
364 int WLevDistance::Mid3( int x
, int y
, int z
)
366 int min
= Min3(x
,y
,z
);
368 return( y
< z
? y
: z
);
370 return( x
< z
? x
: z
);
372 return( x
< y
? x
: y
);
377 // Maximum von drei Werten
378 int WLevDistance::Max3( int x
, int y
, int z
)
381 return( x
> z
? x
: z
);
383 return( y
> z
? y
: z
);
388 // Daten aus CTor initialisieren
389 void WLevDistance::InitData( const sal_Unicode
* cPattern
)
391 cpPattern
= aPatMem
.GetcPtr();
392 bpPatIsWild
= aPatMem
.GetbPtr();
393 npDistance
= aDisMem
.GetPtr();
395 const sal_Unicode
* cp1
= cPattern
;
396 sal_Unicode
* cp2
= cpPattern
;
397 bool* bp
= bpPatIsWild
;
398 // Pattern kopieren, Sternchen zaehlen, escaped Jokers
401 if ( *cp1
== '\\' ) // maybe escaped
403 if ( *(cp1
+1) == '*' || *(cp1
+1) == '?' ) // naechstes Joker?
410 else if ( *cp1
== '*' || *cp1
== '?' ) // Joker
413 nStars
++; // Sternchenzaehler erhoehen
424 WLevDistance::WLevDistance( const sal_Unicode
* cPattern
,
425 int nOtherX
, int nShorterY
, int nLongerZ
,
427 nPatternLen( Impl_WLD_StringLen(cPattern
) ),
428 aPatMem( nPatternLen
+ 1 ),
429 nArrayLen( nPatternLen
+ 1 ),
432 InitData( cPattern
);
433 CalcLPQR( nOtherX
, nShorterY
, nLongerZ
, bRelaxed
);
438 WLevDistance::WLevDistance( const WLevDistance
& rWLD
) :
439 nPatternLen( rWLD
.nPatternLen
),
440 aPatMem( nPatternLen
+ 1 ),
441 nArrayLen( nPatternLen
+ 1 ),
442 aDisMem( nArrayLen
),
443 nLimit( rWLD
.nLimit
),
444 nRepP0( rWLD
.nRepP0
),
445 nInsQ0( rWLD
.nInsQ0
),
446 nDelR0( rWLD
.nDelR0
),
447 nStars( rWLD
.nStars
),
448 bSplitCount( rWLD
.bSplitCount
)
450 cpPattern
= aPatMem
.GetcPtr();
451 bpPatIsWild
= aPatMem
.GetbPtr();
452 npDistance
= aDisMem
.GetPtr();
454 for ( i
=0; i
<nPatternLen
; i
++ )
456 cpPattern
[i
] = rWLD
.cpPattern
[i
];
457 bpPatIsWild
[i
] = rWLD
.bpPatIsWild
[i
];
464 WLevDistance::~WLevDistance()
468 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */