mirror of
https://github.com/ldmud/ldmud
synced 2026-08-12 14:26:05 -04:00
242 lines
7.2 KiB
C
242 lines
7.2 KiB
C
/*
|
|
* diff -- computes the difference between two strings
|
|
* (the minimal number of changes needed to make one equal the other where
|
|
* a change is one of the following:
|
|
* - replacing a character by another one
|
|
* - inserting a character
|
|
* - deleting a character
|
|
* - swapping two consecutive characters)
|
|
*
|
|
* by Alfe for TubMud 99-Mar-10
|
|
* original idea and a first LPC implementation by Ugh
|
|
*
|
|
* Notes about the implementation:
|
|
* The algorithm conducts an exhaustive search of a tree, which nodes
|
|
* consists of the tuple (pos in string a, pos in string b, difference
|
|
* computed while advancing the positions so far). Every node has one
|
|
* to four children:
|
|
* If the characters at the positions match: a new tuple (a+1, b+1, diff).
|
|
* If the characters at the positions don't match:
|
|
* 1. Always: new tuple (a+1, b+1, diff+1)
|
|
* 2. Characters at the position and the next are swapped,
|
|
* new tuple (a+2, b+2, diff+1)
|
|
* 3. Always: Possible insertion in a, new tuple (a+1, b, diff+1)
|
|
* 4. Always: Possible insertion in b, new tuple (a, b+1, diff+1)
|
|
*
|
|
* The implementation exploits that only the 'diff' is required for the
|
|
* result, which allows to keep all tuples in a single list instead
|
|
* of explicitely constructing a tree. The list is sorted by the 'diff'
|
|
* value of the tuples in increasing order. Since the construction of
|
|
* the tree always creates the children for the currently first tuple
|
|
* in the list (which is removed by this), the effect for the tree building
|
|
* is that always the currently most promising branch of all is searched
|
|
* first - it is impossible to get stuck in a local optimum.
|
|
*
|
|
* The disadvantage is the possible high memory usage: for two strings
|
|
* with pairwise swapped characters (e.g. 'abcdef' and 'badcfe') I
|
|
* guesstimate O(3**N) created nodes (N being the length of the strings)
|
|
* and computation costs of O(2**N). I better dig out the functions
|
|
* I wrote in a former life for this problem...
|
|
*/
|
|
|
|
#define DEBUG
|
|
#ifdef DEBUG
|
|
# define _D_ printf("%s %d\n",__FILE__,__LINE__)
|
|
# define _D(x) printf x
|
|
int global_debug_counter = 0;
|
|
#else
|
|
# define _D_
|
|
# define _D(x)
|
|
#endif
|
|
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
|
|
int diff(char *,char *,int);
|
|
void cleanup_diff_pool();
|
|
|
|
void main(int argc,char *argv[]) {
|
|
if (argc != 4) {
|
|
printf("usage: %s <arg1> <arg2> <max>\n",argv[0]);
|
|
exit(1);
|
|
}
|
|
printf("%d\n",diff(argv[1],argv[2],atoi(argv[3])));
|
|
/*
|
|
* cleanup_diff_pool should be called from time to time when memory
|
|
* is needed. to call it here is just one solution (especially in
|
|
* combination with debug output to check that all entries are freed
|
|
* again)
|
|
*/
|
|
cleanup_diff_pool();
|
|
}
|
|
|
|
/*
|
|
* an entry in the list of found positions
|
|
* a -- position in first string
|
|
* b -- position in second string
|
|
* d -- difference at that point
|
|
*/
|
|
typedef struct entry {
|
|
struct entry *next;
|
|
int a,b,d;
|
|
} entry;
|
|
|
|
/*
|
|
* this is a pool of disposed entries which are reused to reduce memory
|
|
* management overhead.
|
|
*/
|
|
entry *diff_pool = NULL;
|
|
|
|
/*
|
|
* make a new entry, fill it with the given values, return it.
|
|
* this function could well use a pool of old disposed entries instead
|
|
* of calling malloc() to create a new entry.
|
|
*/
|
|
entry *make_entry(int a,int b,int d) {
|
|
entry *e;
|
|
#define USE_DISPOSED_POOL
|
|
#ifndef USE_DISPOSED_POOL
|
|
e = malloc(sizeof(entry));
|
|
_D(("mem %p %5d %4d %4d %3d malloc\n",e,global_debug_counter++,a,b,d));
|
|
#else
|
|
if (diff_pool) {
|
|
e = diff_pool;
|
|
diff_pool = diff_pool->next;
|
|
_D(("mem %p %5d %4d %4d %3d reuse\n",e,global_debug_counter++,a,b,d));
|
|
}
|
|
else {
|
|
e = malloc(sizeof(entry));
|
|
_D(("mem %p %5d %4d %4d %3d malloc\n",e,global_debug_counter++,a,b,d));
|
|
}
|
|
#endif
|
|
e->a = a;
|
|
e->b = b;
|
|
e->d = d;
|
|
return e;
|
|
}
|
|
|
|
/*
|
|
* dispose the given entry (e.g. free it); this function could well put
|
|
* the entry into a pool of disposed entries to reduce memory management
|
|
* overhead.
|
|
*/
|
|
void dispose_entry(entry **ep) {
|
|
#ifndef USE_DISPOSED_POOL
|
|
_D(("mem %p %5d %4d %4d %3d free\n",(*ep),global_debug_counter++,
|
|
(*ep)->a,(*ep)->b,(*ep)->d));
|
|
free(*ep);
|
|
#else
|
|
_D(("mem %p %5d %4d %4d %3d dispose\n",(*ep),global_debug_counter++,
|
|
(*ep)->a,(*ep)->b,(*ep)->d));
|
|
(*ep)->next = diff_pool;
|
|
diff_pool = (*ep);
|
|
#endif
|
|
(*ep) = NULL;
|
|
}
|
|
|
|
/*
|
|
* this should cleanup the memory, i.e. free the pool of old disposed entries
|
|
* if any exists. it should be called from time to time in a running system,
|
|
* but not too often to reduce memory management overhead.
|
|
*/
|
|
void cleanup_diff_pool() {
|
|
int counter;
|
|
entry *walker,*e;
|
|
walker = diff_pool;
|
|
for (counter=0; walker; counter++) {
|
|
e = walker;
|
|
walker = walker->next;
|
|
_D(("mem %p %5d %4d %4d %3d free\n",e,global_debug_counter++,
|
|
e->a,e->b,e->d));
|
|
free(e);
|
|
}
|
|
diff_pool = NULL;
|
|
_D(("info %d\n",counter));
|
|
}
|
|
|
|
/*
|
|
* insert the given entry into the given list at the appropriate place.
|
|
*/
|
|
void insert_entry(entry *e,entry **startp) {
|
|
entry *walker;
|
|
if (!(*startp) || // list empty?
|
|
e->d < (*startp)->d) { // less than the start entry?
|
|
e->next = (*startp);
|
|
(*startp) = e;
|
|
return;
|
|
}
|
|
walker = (*startp);
|
|
while (walker->next && e->d >= walker->next->d) {
|
|
if (walker->a == e->a &&
|
|
walker->b == e->b) { // a shorter way to this pos already in list?
|
|
dispose_entry(&e);
|
|
return;
|
|
}
|
|
walker = walker->next;
|
|
}
|
|
// normal case: found place to insert entry
|
|
e->next = walker->next;
|
|
walker->next = e;
|
|
}
|
|
|
|
/*
|
|
* use the first entry of the given queue and produce children of it; add
|
|
* them to the list of entries at the appropriate places.
|
|
*/
|
|
void add_new_entries(char *a,char *b,entry **startp) {
|
|
entry *old;
|
|
int ap,bp,d;
|
|
ap = (*startp)->a;
|
|
bp = (*startp)->b;
|
|
d = (*startp)->d;
|
|
// remove first from list (we will process it now)
|
|
old = (*startp);
|
|
(*startp) = (*startp)->next;
|
|
dispose_entry(&old);
|
|
if (a[ap] && b[bp]) { // both strings not at their end?
|
|
// create usual child (both strings one further)
|
|
if (a[ap] == b[bp]) { // chars equal?
|
|
insert_entry(make_entry(ap+1,bp+1,d),startp);
|
|
return;
|
|
}
|
|
else { // not equal, counts as one difference
|
|
insert_entry(make_entry(ap+1,bp+1,d+1),startp);
|
|
// check for swapped letters: one diff
|
|
if (a[ap+1] && b[bp+1]) // both strings still have one more char?
|
|
if (a[ap] == b[bp+1] &&
|
|
b[bp] == a[ap+1])
|
|
insert_entry(make_entry(ap+2,bp+2,d+1),startp);
|
|
}
|
|
}
|
|
if (a[ap]) { // first not at its end?
|
|
// create difference child (assume missing letter in second: one diff)
|
|
insert_entry(make_entry(ap+1,bp,d+1),startp);
|
|
}
|
|
if (b[bp]) { // second not at its end?
|
|
// create difference child (assume missing letter in first: one diff)
|
|
insert_entry(make_entry(ap,bp+1,d+1),startp);
|
|
}
|
|
}
|
|
|
|
int diff(char *a,char *b,int max) {
|
|
char *s;
|
|
int al,bl,h;
|
|
entry *e,*start;
|
|
al = strlen(a);
|
|
bl = strlen(b);
|
|
start = make_entry(0,0,0); // start value: at pos (0,0) difference 0
|
|
while (start->a < al ||
|
|
start->b < bl) {
|
|
if (max > 0 && start->d >= max) // maximum reached?
|
|
break;
|
|
add_new_entries(a,b,&start);
|
|
}
|
|
h = start->d;
|
|
while (start) {
|
|
e = start;
|
|
start = start->next;
|
|
dispose_entry(&e);
|
|
}
|
|
return h;
|
|
}
|