fstrcmp.c revision 1.1 1 1.1 christos /* Functions to make fuzzy comparisons between strings
2 1.1 christos Copyright (C) 1988-1989, 1992-1993, 1995, 2001-2003, 2006 Free Software Foundation, Inc.
3 1.1 christos
4 1.1 christos This program is free software; you can redistribute it and/or modify
5 1.1 christos it under the terms of the GNU General Public License as published by
6 1.1 christos the Free Software Foundation; either version 2 of the License, or (at
7 1.1 christos your option) any later version.
8 1.1 christos
9 1.1 christos This program is distributed in the hope that it will be useful, but
10 1.1 christos WITHOUT ANY WARRANTY; without even the implied warranty of
11 1.1 christos MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 1.1 christos General Public License for more details.
13 1.1 christos
14 1.1 christos You should have received a copy of the GNU General Public License
15 1.1 christos along with this program; if not, write to the Free Software
16 1.1 christos Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
17 1.1 christos
18 1.1 christos
19 1.1 christos Derived from GNU diff 2.7, analyze.c et al.
20 1.1 christos
21 1.1 christos The basic idea is to consider two strings as similar if, when
22 1.1 christos transforming the first string into the second string through a
23 1.1 christos sequence of edits (inserts and deletes of one character each),
24 1.1 christos this sequence is short - or equivalently, if the ordered list
25 1.1 christos of characters that are untouched by these edits is long. For a
26 1.1 christos good introduction to the subject, read about the "Levenshtein
27 1.1 christos distance" in Wikipedia.
28 1.1 christos
29 1.1 christos The basic algorithm is described in:
30 1.1 christos "An O(ND) Difference Algorithm and its Variations", Eugene Myers,
31 1.1 christos Algorithmica Vol. 1 No. 2, 1986, pp. 251-266;
32 1.1 christos see especially section 4.2, which describes the variation used below.
33 1.1 christos
34 1.1 christos The basic algorithm was independently discovered as described in:
35 1.1 christos "Algorithms for Approximate String Matching", E. Ukkonen,
36 1.1 christos Information and Control Vol. 64, 1985, pp. 100-118.
37 1.1 christos
38 1.1 christos Unless the 'minimal' flag is set, this code uses the TOO_EXPENSIVE
39 1.1 christos heuristic, by Paul Eggert, to limit the cost to O(N**1.5 log N)
40 1.1 christos at the price of producing suboptimal output for large inputs with
41 1.1 christos many differences.
42 1.1 christos
43 1.1 christos Modified to work on strings rather than files
44 1.1 christos by Peter Miller <pmiller (at) agso.gov.au>, October 1995 */
45 1.1 christos
46 1.1 christos #include <config.h>
47 1.1 christos
48 1.1 christos /* Specification. */
49 1.1 christos #include "fstrcmp.h"
50 1.1 christos
51 1.1 christos #include <string.h>
52 1.1 christos #include <stdio.h>
53 1.1 christos #include <stdlib.h>
54 1.1 christos #include <limits.h>
55 1.1 christos
56 1.1 christos #include "lock.h"
57 1.1 christos #include "tls.h"
58 1.1 christos #include "xalloc.h"
59 1.1 christos
60 1.1 christos #ifndef uintptr_t
61 1.1 christos # define uintptr_t unsigned long
62 1.1 christos #endif
63 1.1 christos
64 1.1 christos
65 1.1 christos /*
66 1.1 christos * Context of comparison operation.
67 1.1 christos */
68 1.1 christos struct context
69 1.1 christos {
70 1.1 christos /*
71 1.1 christos * Data on one input string being compared.
72 1.1 christos */
73 1.1 christos struct string_data
74 1.1 christos {
75 1.1 christos /* The string to be compared. */
76 1.1 christos const char *data;
77 1.1 christos
78 1.1 christos /* The length of the string to be compared. */
79 1.1 christos int data_length;
80 1.1 christos
81 1.1 christos /* The number of characters inserted or deleted. */
82 1.1 christos int edit_count;
83 1.1 christos }
84 1.1 christos string[2];
85 1.1 christos
86 1.1 christos #ifdef MINUS_H_FLAG
87 1.1 christos
88 1.1 christos /* This corresponds to the diff -H flag. With this heuristic, for
89 1.1 christos strings with a constant small density of changes, the algorithm is
90 1.1 christos linear in the strings size. This is unlikely in typical uses of
91 1.1 christos fstrcmp, and so is usually compiled out. Besides, there is no
92 1.1 christos interface to set it true. */
93 1.1 christos int heuristic;
94 1.1 christos
95 1.1 christos #endif
96 1.1 christos
97 1.1 christos /* Vector, indexed by diagonal, containing 1 + the X coordinate of the
98 1.1 christos point furthest along the given diagonal in the forward search of the
99 1.1 christos edit matrix. */
100 1.1 christos int *fdiag;
101 1.1 christos
102 1.1 christos /* Vector, indexed by diagonal, containing the X coordinate of the point
103 1.1 christos furthest along the given diagonal in the backward search of the edit
104 1.1 christos matrix. */
105 1.1 christos int *bdiag;
106 1.1 christos
107 1.1 christos /* Edit scripts longer than this are too expensive to compute. */
108 1.1 christos int too_expensive;
109 1.1 christos
110 1.1 christos /* Snakes bigger than this are considered `big'. */
111 1.1 christos #define SNAKE_LIMIT 20
112 1.1 christos };
113 1.1 christos
114 1.1 christos struct partition
115 1.1 christos {
116 1.1 christos /* Midpoints of this partition. */
117 1.1 christos int xmid, ymid;
118 1.1 christos
119 1.1 christos /* Nonzero if low half will be analyzed minimally. */
120 1.1 christos int lo_minimal;
121 1.1 christos
122 1.1 christos /* Likewise for high half. */
123 1.1 christos int hi_minimal;
124 1.1 christos };
125 1.1 christos
126 1.1 christos
127 1.1 christos /* NAME
128 1.1 christos diag - find diagonal path
129 1.1 christos
130 1.1 christos SYNOPSIS
131 1.1 christos int diag(int xoff, int xlim, int yoff, int ylim, int minimal,
132 1.1 christos struct partition *part, struct context *ctxt);
133 1.1 christos
134 1.1 christos DESCRIPTION
135 1.1 christos Find the midpoint of the shortest edit script for a specified
136 1.1 christos portion of the two strings.
137 1.1 christos
138 1.1 christos Scan from the beginnings of the strings, and simultaneously from
139 1.1 christos the ends, doing a breadth-first search through the space of
140 1.1 christos edit-sequence. When the two searches meet, we have found the
141 1.1 christos midpoint of the shortest edit sequence.
142 1.1 christos
143 1.1 christos If MINIMAL is nonzero, find the minimal edit script regardless
144 1.1 christos of expense. Otherwise, if the search is too expensive, use
145 1.1 christos heuristics to stop the search and report a suboptimal answer.
146 1.1 christos
147 1.1 christos RETURNS
148 1.1 christos Set PART->(XMID,YMID) to the midpoint (XMID,YMID). The diagonal
149 1.1 christos number XMID - YMID equals the number of inserted characters
150 1.1 christos minus the number of deleted characters (counting only characters
151 1.1 christos before the midpoint). Return the approximate edit cost; this is
152 1.1 christos the total number of characters inserted or deleted (counting
153 1.1 christos only characters before the midpoint), unless a heuristic is used
154 1.1 christos to terminate the search prematurely.
155 1.1 christos
156 1.1 christos Set PART->LEFT_MINIMAL to nonzero iff the minimal edit script
157 1.1 christos for the left half of the partition is known; similarly for
158 1.1 christos PART->RIGHT_MINIMAL.
159 1.1 christos
160 1.1 christos CAVEAT
161 1.1 christos This function assumes that the first characters of the specified
162 1.1 christos portions of the two strings do not match, and likewise that the
163 1.1 christos last characters do not match. The caller must trim matching
164 1.1 christos characters from the beginning and end of the portions it is
165 1.1 christos going to specify.
166 1.1 christos
167 1.1 christos If we return the "wrong" partitions, the worst this can do is
168 1.1 christos cause suboptimal diff output. It cannot cause incorrect diff
169 1.1 christos output. */
170 1.1 christos
171 1.1 christos static int
172 1.1 christos diag (int xoff, int xlim, int yoff, int ylim, int minimal,
173 1.1 christos struct partition *part, struct context *ctxt)
174 1.1 christos {
175 1.1 christos int *const fd = ctxt->fdiag; /* Give the compiler a chance. */
176 1.1 christos int *const bd = ctxt->bdiag; /* Additional help for the compiler. */
177 1.1 christos const char *const xv = ctxt->string[0].data; /* Still more help for the compiler. */
178 1.1 christos const char *const yv = ctxt->string[1].data; /* And more and more . . . */
179 1.1 christos const int dmin = xoff - ylim; /* Minimum valid diagonal. */
180 1.1 christos const int dmax = xlim - yoff; /* Maximum valid diagonal. */
181 1.1 christos const int fmid = xoff - yoff; /* Center diagonal of top-down search. */
182 1.1 christos const int bmid = xlim - ylim; /* Center diagonal of bottom-up search. */
183 1.1 christos int fmin = fmid;
184 1.1 christos int fmax = fmid; /* Limits of top-down search. */
185 1.1 christos int bmin = bmid;
186 1.1 christos int bmax = bmid; /* Limits of bottom-up search. */
187 1.1 christos int c; /* Cost. */
188 1.1 christos int odd = (fmid - bmid) & 1;
189 1.1 christos
190 1.1 christos /*
191 1.1 christos * True if southeast corner is on an odd diagonal with respect
192 1.1 christos * to the northwest.
193 1.1 christos */
194 1.1 christos fd[fmid] = xoff;
195 1.1 christos bd[bmid] = xlim;
196 1.1 christos for (c = 1;; ++c)
197 1.1 christos {
198 1.1 christos int d; /* Active diagonal. */
199 1.1 christos int big_snake;
200 1.1 christos
201 1.1 christos big_snake = 0;
202 1.1 christos /* Extend the top-down search by an edit step in each diagonal. */
203 1.1 christos if (fmin > dmin)
204 1.1 christos fd[--fmin - 1] = -1;
205 1.1 christos else
206 1.1 christos ++fmin;
207 1.1 christos if (fmax < dmax)
208 1.1 christos fd[++fmax + 1] = -1;
209 1.1 christos else
210 1.1 christos --fmax;
211 1.1 christos for (d = fmax; d >= fmin; d -= 2)
212 1.1 christos {
213 1.1 christos int x;
214 1.1 christos int y;
215 1.1 christos int oldx;
216 1.1 christos int tlo;
217 1.1 christos int thi;
218 1.1 christos
219 1.1 christos tlo = fd[d - 1],
220 1.1 christos thi = fd[d + 1];
221 1.1 christos
222 1.1 christos if (tlo >= thi)
223 1.1 christos x = tlo + 1;
224 1.1 christos else
225 1.1 christos x = thi;
226 1.1 christos oldx = x;
227 1.1 christos y = x - d;
228 1.1 christos while (x < xlim && y < ylim && xv[x] == yv[y])
229 1.1 christos {
230 1.1 christos ++x;
231 1.1 christos ++y;
232 1.1 christos }
233 1.1 christos if (x - oldx > SNAKE_LIMIT)
234 1.1 christos big_snake = 1;
235 1.1 christos fd[d] = x;
236 1.1 christos if (odd && bmin <= d && d <= bmax && bd[d] <= x)
237 1.1 christos {
238 1.1 christos part->xmid = x;
239 1.1 christos part->ymid = y;
240 1.1 christos part->lo_minimal = part->hi_minimal = 1;
241 1.1 christos return 2 * c - 1;
242 1.1 christos }
243 1.1 christos }
244 1.1 christos /* Similarly extend the bottom-up search. */
245 1.1 christos if (bmin > dmin)
246 1.1 christos bd[--bmin - 1] = INT_MAX;
247 1.1 christos else
248 1.1 christos ++bmin;
249 1.1 christos if (bmax < dmax)
250 1.1 christos bd[++bmax + 1] = INT_MAX;
251 1.1 christos else
252 1.1 christos --bmax;
253 1.1 christos for (d = bmax; d >= bmin; d -= 2)
254 1.1 christos {
255 1.1 christos int x;
256 1.1 christos int y;
257 1.1 christos int oldx;
258 1.1 christos int tlo;
259 1.1 christos int thi;
260 1.1 christos
261 1.1 christos tlo = bd[d - 1],
262 1.1 christos thi = bd[d + 1];
263 1.1 christos if (tlo < thi)
264 1.1 christos x = tlo;
265 1.1 christos else
266 1.1 christos x = thi - 1;
267 1.1 christos oldx = x;
268 1.1 christos y = x - d;
269 1.1 christos while (x > xoff && y > yoff && xv[x - 1] == yv[y - 1])
270 1.1 christos {
271 1.1 christos --x;
272 1.1 christos --y;
273 1.1 christos }
274 1.1 christos if (oldx - x > SNAKE_LIMIT)
275 1.1 christos big_snake = 1;
276 1.1 christos bd[d] = x;
277 1.1 christos if (!odd && fmin <= d && d <= fmax && x <= fd[d])
278 1.1 christos {
279 1.1 christos part->xmid = x;
280 1.1 christos part->ymid = y;
281 1.1 christos part->lo_minimal = part->hi_minimal = 1;
282 1.1 christos return 2 * c;
283 1.1 christos }
284 1.1 christos }
285 1.1 christos
286 1.1 christos if (minimal)
287 1.1 christos continue;
288 1.1 christos
289 1.1 christos #ifdef MINUS_H_FLAG
290 1.1 christos /* Heuristic: check occasionally for a diagonal that has made lots
291 1.1 christos of progress compared with the edit distance. If we have any
292 1.1 christos such, find the one that has made the most progress and return
293 1.1 christos it as if it had succeeded.
294 1.1 christos
295 1.1 christos With this heuristic, for strings with a constant small density
296 1.1 christos of changes, the algorithm is linear in the strings size. */
297 1.1 christos if (c > 200 && big_snake && ctxt->heuristic)
298 1.1 christos {
299 1.1 christos int best;
300 1.1 christos
301 1.1 christos best = 0;
302 1.1 christos for (d = fmax; d >= fmin; d -= 2)
303 1.1 christos {
304 1.1 christos int dd;
305 1.1 christos int x;
306 1.1 christos int y;
307 1.1 christos int v;
308 1.1 christos
309 1.1 christos dd = d - fmid;
310 1.1 christos x = fd[d];
311 1.1 christos y = x - d;
312 1.1 christos v = (x - xoff) * 2 - dd;
313 1.1 christos
314 1.1 christos if (v > 12 * (c + (dd < 0 ? -dd : dd)))
315 1.1 christos {
316 1.1 christos if
317 1.1 christos (
318 1.1 christos v > best
319 1.1 christos &&
320 1.1 christos xoff + SNAKE_LIMIT <= x
321 1.1 christos &&
322 1.1 christos x < xlim
323 1.1 christos &&
324 1.1 christos yoff + SNAKE_LIMIT <= y
325 1.1 christos &&
326 1.1 christos y < ylim
327 1.1 christos )
328 1.1 christos {
329 1.1 christos /* We have a good enough best diagonal; now insist
330 1.1 christos that it end with a significant snake. */
331 1.1 christos int k;
332 1.1 christos
333 1.1 christos for (k = 1; xv[x - k] == yv[y - k]; k++)
334 1.1 christos {
335 1.1 christos if (k == SNAKE_LIMIT)
336 1.1 christos {
337 1.1 christos best = v;
338 1.1 christos part->xmid = x;
339 1.1 christos part->ymid = y;
340 1.1 christos break;
341 1.1 christos }
342 1.1 christos }
343 1.1 christos }
344 1.1 christos }
345 1.1 christos }
346 1.1 christos if (best > 0)
347 1.1 christos {
348 1.1 christos part->lo_minimal = 1;
349 1.1 christos part->hi_minimal = 0;
350 1.1 christos return 2 * c - 1;
351 1.1 christos }
352 1.1 christos best = 0;
353 1.1 christos for (d = bmax; d >= bmin; d -= 2)
354 1.1 christos {
355 1.1 christos int dd;
356 1.1 christos int x;
357 1.1 christos int y;
358 1.1 christos int v;
359 1.1 christos
360 1.1 christos dd = d - bmid;
361 1.1 christos x = bd[d];
362 1.1 christos y = x - d;
363 1.1 christos v = (xlim - x) * 2 + dd;
364 1.1 christos
365 1.1 christos if (v > 12 * (c + (dd < 0 ? -dd : dd)))
366 1.1 christos {
367 1.1 christos if (v > best && xoff < x && x <= xlim - SNAKE_LIMIT &&
368 1.1 christos yoff < y && y <= ylim - SNAKE_LIMIT)
369 1.1 christos {
370 1.1 christos /* We have a good enough best diagonal; now insist
371 1.1 christos that it end with a significant snake. */
372 1.1 christos int k;
373 1.1 christos
374 1.1 christos for (k = 0; xv[x + k] == yv[y + k]; k++)
375 1.1 christos {
376 1.1 christos if (k == SNAKE_LIMIT - 1)
377 1.1 christos {
378 1.1 christos best = v;
379 1.1 christos part->xmid = x;
380 1.1 christos part->ymid = y;
381 1.1 christos break;
382 1.1 christos }
383 1.1 christos }
384 1.1 christos }
385 1.1 christos }
386 1.1 christos }
387 1.1 christos if (best > 0)
388 1.1 christos {
389 1.1 christos part->lo_minimal = 0;
390 1.1 christos part->hi_minimal = 1;
391 1.1 christos return 2 * c - 1;
392 1.1 christos }
393 1.1 christos }
394 1.1 christos #endif /* MINUS_H_FLAG */
395 1.1 christos
396 1.1 christos /* Heuristic: if we've gone well beyond the call of duty, give up
397 1.1 christos and report halfway between our best results so far. */
398 1.1 christos if (c >= ctxt->too_expensive)
399 1.1 christos {
400 1.1 christos int fxybest;
401 1.1 christos int fxbest;
402 1.1 christos int bxybest;
403 1.1 christos int bxbest;
404 1.1 christos
405 1.1 christos /* Pacify `gcc -Wall'. */
406 1.1 christos fxbest = 0;
407 1.1 christos bxbest = 0;
408 1.1 christos
409 1.1 christos /* Find forward diagonal that maximizes X + Y. */
410 1.1 christos fxybest = -1;
411 1.1 christos for (d = fmax; d >= fmin; d -= 2)
412 1.1 christos {
413 1.1 christos int x;
414 1.1 christos int y;
415 1.1 christos
416 1.1 christos x = fd[d] < xlim ? fd[d] : xlim;
417 1.1 christos y = x - d;
418 1.1 christos
419 1.1 christos if (ylim < y)
420 1.1 christos {
421 1.1 christos x = ylim + d;
422 1.1 christos y = ylim;
423 1.1 christos }
424 1.1 christos if (fxybest < x + y)
425 1.1 christos {
426 1.1 christos fxybest = x + y;
427 1.1 christos fxbest = x;
428 1.1 christos }
429 1.1 christos }
430 1.1 christos /* Find backward diagonal that minimizes X + Y. */
431 1.1 christos bxybest = INT_MAX;
432 1.1 christos for (d = bmax; d >= bmin; d -= 2)
433 1.1 christos {
434 1.1 christos int x;
435 1.1 christos int y;
436 1.1 christos
437 1.1 christos x = xoff > bd[d] ? xoff : bd[d];
438 1.1 christos y = x - d;
439 1.1 christos
440 1.1 christos if (y < yoff)
441 1.1 christos {
442 1.1 christos x = yoff + d;
443 1.1 christos y = yoff;
444 1.1 christos }
445 1.1 christos if (x + y < bxybest)
446 1.1 christos {
447 1.1 christos bxybest = x + y;
448 1.1 christos bxbest = x;
449 1.1 christos }
450 1.1 christos }
451 1.1 christos /* Use the better of the two diagonals. */
452 1.1 christos if ((xlim + ylim) - bxybest < fxybest - (xoff + yoff))
453 1.1 christos {
454 1.1 christos part->xmid = fxbest;
455 1.1 christos part->ymid = fxybest - fxbest;
456 1.1 christos part->lo_minimal = 1;
457 1.1 christos part->hi_minimal = 0;
458 1.1 christos }
459 1.1 christos else
460 1.1 christos {
461 1.1 christos part->xmid = bxbest;
462 1.1 christos part->ymid = bxybest - bxbest;
463 1.1 christos part->lo_minimal = 0;
464 1.1 christos part->hi_minimal = 1;
465 1.1 christos }
466 1.1 christos return 2 * c - 1;
467 1.1 christos }
468 1.1 christos }
469 1.1 christos }
470 1.1 christos
471 1.1 christos
472 1.1 christos /* NAME
473 1.1 christos compareseq - find edit sequence
474 1.1 christos
475 1.1 christos SYNOPSIS
476 1.1 christos void compareseq(int xoff, int xlim, int yoff, int ylim, int minimal,
477 1.1 christos struct context *ctxt);
478 1.1 christos
479 1.1 christos DESCRIPTION
480 1.1 christos Compare in detail contiguous subsequences of the two strings
481 1.1 christos which are known, as a whole, to match each other.
482 1.1 christos
483 1.1 christos The subsequence of string 0 is [XOFF, XLIM) and likewise for
484 1.1 christos string 1.
485 1.1 christos
486 1.1 christos Note that XLIM, YLIM are exclusive bounds. All character
487 1.1 christos numbers are origin-0.
488 1.1 christos
489 1.1 christos If MINIMAL is nonzero, find a minimal difference no matter how
490 1.1 christos expensive it is. */
491 1.1 christos
492 1.1 christos static void
493 1.1 christos compareseq (int xoff, int xlim, int yoff, int ylim, int minimal,
494 1.1 christos struct context *ctxt)
495 1.1 christos {
496 1.1 christos const char *const xv = ctxt->string[0].data; /* Help the compiler. */
497 1.1 christos const char *const yv = ctxt->string[1].data;
498 1.1 christos
499 1.1 christos /* Slide down the bottom initial diagonal. */
500 1.1 christos while (xoff < xlim && yoff < ylim && xv[xoff] == yv[yoff])
501 1.1 christos {
502 1.1 christos ++xoff;
503 1.1 christos ++yoff;
504 1.1 christos }
505 1.1 christos
506 1.1 christos /* Slide up the top initial diagonal. */
507 1.1 christos while (xlim > xoff && ylim > yoff && xv[xlim - 1] == yv[ylim - 1])
508 1.1 christos {
509 1.1 christos --xlim;
510 1.1 christos --ylim;
511 1.1 christos }
512 1.1 christos
513 1.1 christos /* Handle simple cases. */
514 1.1 christos if (xoff == xlim)
515 1.1 christos {
516 1.1 christos while (yoff < ylim)
517 1.1 christos {
518 1.1 christos ctxt->string[1].edit_count++;
519 1.1 christos ++yoff;
520 1.1 christos }
521 1.1 christos }
522 1.1 christos else if (yoff == ylim)
523 1.1 christos {
524 1.1 christos while (xoff < xlim)
525 1.1 christos {
526 1.1 christos ctxt->string[0].edit_count++;
527 1.1 christos ++xoff;
528 1.1 christos }
529 1.1 christos }
530 1.1 christos else
531 1.1 christos {
532 1.1 christos int c;
533 1.1 christos struct partition part;
534 1.1 christos
535 1.1 christos /* Find a point of correspondence in the middle of the strings. */
536 1.1 christos c = diag (xoff, xlim, yoff, ylim, minimal, &part, ctxt);
537 1.1 christos if (c == 1)
538 1.1 christos {
539 1.1 christos #if 0
540 1.1 christos /* This should be impossible, because it implies that one of
541 1.1 christos the two subsequences is empty, and that case was handled
542 1.1 christos above without calling `diag'. Let's verify that this is
543 1.1 christos true. */
544 1.1 christos abort ();
545 1.1 christos #else
546 1.1 christos /* The two subsequences differ by a single insert or delete;
547 1.1 christos record it and we are done. */
548 1.1 christos if (part.xmid - part.ymid < xoff - yoff)
549 1.1 christos ctxt->string[1].edit_count++;
550 1.1 christos else
551 1.1 christos ctxt->string[0].edit_count++;
552 1.1 christos #endif
553 1.1 christos }
554 1.1 christos else
555 1.1 christos {
556 1.1 christos /* Use the partitions to split this problem into subproblems. */
557 1.1 christos compareseq (xoff, part.xmid, yoff, part.ymid, part.lo_minimal, ctxt);
558 1.1 christos compareseq (part.xmid, xlim, part.ymid, ylim, part.hi_minimal, ctxt);
559 1.1 christos }
560 1.1 christos }
561 1.1 christos }
562 1.1 christos
563 1.1 christos
564 1.1 christos /* Because fstrcmp is typically called multiple times, attempt to minimize
565 1.1 christos the number of memory allocations performed. Thus, let a call reuse the
566 1.1 christos memory already allocated by the previous call, if it is sufficient.
567 1.1 christos To make it multithread-safe, without need for a lock that protects the
568 1.1 christos already allocated memory, store the allocated memory per thread. Free
569 1.1 christos it only when the thread exits. */
570 1.1 christos
571 1.1 christos static gl_tls_key_t buffer_key; /* TLS key for a 'int *' */
572 1.1 christos static gl_tls_key_t bufmax_key; /* TLS key for a 'size_t' */
573 1.1 christos
574 1.1 christos static void
575 1.1 christos keys_init (void)
576 1.1 christos {
577 1.1 christos gl_tls_key_init (buffer_key, free);
578 1.1 christos gl_tls_key_init (bufmax_key, NULL);
579 1.1 christos /* The per-thread initial values are NULL and 0, respectively. */
580 1.1 christos }
581 1.1 christos
582 1.1 christos /* Ensure that keys_init is called once only. */
583 1.1 christos gl_once_define(static, keys_init_once)
584 1.1 christos
585 1.1 christos
586 1.1 christos /* NAME
587 1.1 christos fstrcmp - fuzzy string compare
588 1.1 christos
589 1.1 christos SYNOPSIS
590 1.1 christos double fstrcmp(const char *, const char *);
591 1.1 christos
592 1.1 christos DESCRIPTION
593 1.1 christos The fstrcmp function may be used to compare two string for
594 1.1 christos similarity. It is very useful in reducing "cascade" or
595 1.1 christos "secondary" errors in compilers or other situations where
596 1.1 christos symbol tables occur.
597 1.1 christos
598 1.1 christos RETURNS
599 1.1 christos double; 0 if the strings are entirly dissimilar, 1 if the
600 1.1 christos strings are identical, and a number in between if they are
601 1.1 christos similar. */
602 1.1 christos
603 1.1 christos double
604 1.1 christos fstrcmp (const char *string1, const char *string2)
605 1.1 christos {
606 1.1 christos struct context ctxt;
607 1.1 christos int i;
608 1.1 christos
609 1.1 christos size_t fdiag_len;
610 1.1 christos int *buffer;
611 1.1 christos size_t bufmax;
612 1.1 christos
613 1.1 christos /* set the info for each string. */
614 1.1 christos ctxt.string[0].data = string1;
615 1.1 christos ctxt.string[0].data_length = strlen (string1);
616 1.1 christos ctxt.string[1].data = string2;
617 1.1 christos ctxt.string[1].data_length = strlen (string2);
618 1.1 christos
619 1.1 christos /* short-circuit obvious comparisons */
620 1.1 christos if (ctxt.string[0].data_length == 0 && ctxt.string[1].data_length == 0)
621 1.1 christos return 1.0;
622 1.1 christos if (ctxt.string[0].data_length == 0 || ctxt.string[1].data_length == 0)
623 1.1 christos return 0.0;
624 1.1 christos
625 1.1 christos /* Set TOO_EXPENSIVE to be approximate square root of input size,
626 1.1 christos bounded below by 256. */
627 1.1 christos ctxt.too_expensive = 1;
628 1.1 christos for (i = ctxt.string[0].data_length + ctxt.string[1].data_length;
629 1.1 christos i != 0;
630 1.1 christos i >>= 2)
631 1.1 christos ctxt.too_expensive <<= 1;
632 1.1 christos if (ctxt.too_expensive < 256)
633 1.1 christos ctxt.too_expensive = 256;
634 1.1 christos
635 1.1 christos /* Allocate memory for fdiag and bdiag from a thread-local pool. */
636 1.1 christos fdiag_len = ctxt.string[0].data_length + ctxt.string[1].data_length + 3;
637 1.1 christos gl_once (keys_init_once, keys_init);
638 1.1 christos buffer = (int *) gl_tls_get (buffer_key);
639 1.1 christos bufmax = (size_t) (uintptr_t) gl_tls_get (bufmax_key);
640 1.1 christos if (fdiag_len > bufmax)
641 1.1 christos {
642 1.1 christos /* Need more memory. */
643 1.1 christos bufmax = 2 * bufmax;
644 1.1 christos if (fdiag_len > bufmax)
645 1.1 christos bufmax = fdiag_len;
646 1.1 christos /* Calling xrealloc would be a waste: buffer's contents does not need
647 1.1 christos to be preserved. */
648 1.1 christos if (buffer != NULL)
649 1.1 christos free (buffer);
650 1.1 christos buffer = (int *) xmalloc (bufmax * (2 * sizeof (int)));
651 1.1 christos gl_tls_set (buffer_key, buffer);
652 1.1 christos gl_tls_set (bufmax_key, (void *) (uintptr_t) bufmax);
653 1.1 christos }
654 1.1 christos ctxt.fdiag = buffer + ctxt.string[1].data_length + 1;
655 1.1 christos ctxt.bdiag = ctxt.fdiag + fdiag_len;
656 1.1 christos
657 1.1 christos /* Now do the main comparison algorithm */
658 1.1 christos ctxt.string[0].edit_count = 0;
659 1.1 christos ctxt.string[1].edit_count = 0;
660 1.1 christos compareseq (0, ctxt.string[0].data_length, 0, ctxt.string[1].data_length, 0,
661 1.1 christos &ctxt);
662 1.1 christos
663 1.1 christos /* The result is
664 1.1 christos ((number of chars in common) / (average length of the strings)).
665 1.1 christos This is admittedly biased towards finding that the strings are
666 1.1 christos similar, however it does produce meaningful results. */
667 1.1 christos return ((double) (ctxt.string[0].data_length + ctxt.string[1].data_length
668 1.1 christos - ctxt.string[1].edit_count - ctxt.string[0].edit_count)
669 1.1 christos / (ctxt.string[0].data_length + ctxt.string[1].data_length));
670 1.1 christos }
671