]> git.ozlabs.org Git - ccan/blob - ccan/strgrp/strgrp.c
00e0bf7152c32e73dc99e5fb454f224fae61852f
[ccan] / ccan / strgrp / strgrp.c
1 /*
2     Group similar strings
3     Copyright (C) 2014  Andrew Jeffery <andrew@aj.id.au>
4
5     This program is free software: you can redistribute it and/or modify
6     it under the terms of the GNU General Public License as published by
7     the Free Software Foundation, either version 3 of the License, or
8     (at your option) any later version.
9
10     This program is distributed in the hope that it will be useful,
11     but WITHOUT ANY WARRANTY; without even the implied warranty of
12     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13     GNU Lesser General Public License for more details.
14
15     You should have received a copy of the GNU Lesser General Public License
16     along with this program.  If not, see <http://www.gnu.org/licenses/>.
17 */
18 #include <assert.h>
19 #include <limits.h>
20 #include <math.h>
21 #include <stdbool.h>
22 #include <stdint.h>
23 #include <stdio.h>
24 #include <stdlib.h>
25 #include <string.h>
26 #include "ccan/darray/darray.h"
27 #include "ccan/stringmap/stringmap.h"
28 #include "ccan/tal/tal.h"
29 #include "ccan/tal/str/str.h"
30 #include "strgrp.h"
31 #include "config.h"
32
33 #define CHAR_N_VALUES (1 << CHAR_BIT)
34
35 typedef darray(struct strgrp_grp *) darray_grp;
36 typedef darray(struct strgrp_item *) darray_item;
37
38 typedef stringmap(struct strgrp_grp *) stringmap_grp;
39
40 struct grp_score {
41     struct strgrp_grp *grp;
42     double score;
43 };
44
45 typedef darray(struct grp_score *) darray_score;
46
47 struct strgrp {
48     double threshold;
49     stringmap_grp known;
50     unsigned int n_grps;
51     darray_grp grps;
52     struct grp_score *scores;
53     int16_t pop[CHAR_N_VALUES];
54 };
55
56 struct strgrp_iter {
57     const struct strgrp *ctx;
58     int i;
59 };
60
61 struct strgrp_grp {
62     const char *key;
63     size_t key_len;
64     darray_item items;
65     int32_t n_items;
66     int16_t pop[CHAR_N_VALUES];
67 };
68
69 struct strgrp_grp_iter {
70     const struct strgrp_grp *grp;
71     int i;
72 };
73
74 struct strgrp_item {
75     const char *key;
76     void *value;
77 };
78
79
80 /* String vector cosine similarity[1]
81  *
82  * [1] http://blog.nishtahir.com/2015/09/19/fuzzy-string-matching-using-cosine-similarity/
83  */
84
85 static inline void
86 strpopcnt(const char *const str, int16_t pop[CHAR_N_VALUES]) {
87     const char *c;
88     memset(pop, 0, CHAR_N_VALUES * sizeof(*pop));
89     for(c = str; *c; c++) {
90         assert(*c >= 0);
91         pop[(unsigned char)*c]++;
92     }
93 }
94
95 static inline double
96 strcossim(const int16_t ref[CHAR_N_VALUES], const int16_t key[CHAR_N_VALUES]) {
97     int32_t saibi = 0;
98     int32_t sai2 = 0;
99     int32_t sbi2 = 0;
100     size_t i;
101     for (i = 0; i < CHAR_N_VALUES; i++) {
102         saibi += ref[i] * key[i];
103         sai2 += ref[i] * ref[i];
104         sbi2 += key[i] * key[i];
105     }
106     return saibi / (sqrt(sai2) * sqrt(sbi2));
107 }
108
109 /* Low-cost filter functions */
110
111 static inline bool
112 should_grp_score_cos(const struct strgrp *const ctx,
113         struct strgrp_grp *const grp, const char *const str) {
114     return ctx->threshold <= strcossim(ctx->pop, grp->pop);
115 }
116
117 static inline bool
118 should_grp_score_len(const struct strgrp *const ctx,
119         const struct strgrp_grp *const grp, const char *const str) {
120     const size_t strl = strlen(str);
121     const size_t keyl = grp->key_len;
122     double sr =  strl / keyl;
123     if (1 < sr) {
124         sr = 1 / sr;
125     }
126     return ctx->threshold <= sr;
127 }
128
129 /* Scoring - Longest Common Subsequence[2]
130  *
131  * [2] https://en.wikipedia.org/wiki/Longest_common_subsequence_problem
132  */
133 #define ROWS 2
134
135 static inline int cmi(int i, int j) {
136     return ROWS * j + i;
137 }
138
139 static inline int16_t
140 lcs(const char *const a, const char *const b) {
141     const int lb = strlen(b);
142     const int lbp1 = lb + 1;
143     int16_t *const lookup = calloc(ROWS * lbp1, sizeof(int16_t));
144     if (!lookup) {
145         return -1;
146     }
147     int ia, ib;
148     for (ia = (strlen(a) - 1); ia >= 0; ia--) {
149         const char iav = a[ia];
150         for (ib = lb - 1; ib >= 0; ib--) {
151             const char ibv = b[ib];
152             const int ial = (ia + 1) & 1; // ia last
153             const int iac = ia & 1; // ia current
154             const int ibl = ib + 1; // ib last
155             // don't need separate "ib current" as it's just ib
156             if (iav == ibv) {
157                 lookup[cmi(iac, ib)] = 1 + lookup[cmi(ial, ibl)];
158             } else {
159                 const int16_t valb = lookup[cmi(ial, ib)];
160                 const int16_t vabl = lookup[cmi(iac, ibl)];
161                 lookup[cmi(iac, ib)] = (valb > vabl) ? valb : vabl;
162             }
163         }
164     }
165     int16_t result = lookup[0];
166     free(lookup);
167     return result;
168 }
169
170 #undef ROWS
171
172 static inline double
173 nlcs(const char *const a, const char *const b) {
174     const double lcss = lcs(a, b);
175     return 2 * lcss / (strlen(a) + strlen(b));
176 }
177
178 static inline double
179 grp_score(const struct strgrp_grp *const grp, const char *const str) {
180     return nlcs(grp->key, str);
181 }
182
183 /* Structure management */
184
185 static struct strgrp_item *
186 new_item(tal_t *const tctx, const char *const str, void *const data) {
187     struct strgrp_item *i = talz(tctx, struct strgrp_item);
188     if (!i) {
189         return NULL;
190     }
191     i->key = tal_strdup(i, str);
192     i->value = data;
193     return i;
194 }
195
196 static bool
197 add_item(struct strgrp_grp *const ctx, const char *const str,
198         void *const data) {
199     struct strgrp_item *i = new_item(ctx, str, data);
200     if (!i) {
201         return false;
202     }
203     darray_push(ctx->items, i);
204     ctx->n_items++;
205     return true;
206 }
207
208 static void
209 free_grp(struct strgrp_grp *grp) {
210     darray_free(grp->items);
211 }
212
213 static struct strgrp_grp *
214 new_grp(tal_t *const tctx, const char *const str, void *const data) {
215     struct strgrp_grp *b = talz(tctx, struct strgrp_grp);
216     if (!b) {
217         return NULL;
218     }
219     b->key = tal_strdup(b, str);
220     b->key_len = strlen(str);
221     b->n_items = 0;
222     darray_init(b->items);
223     tal_add_destructor(b, free_grp);
224     if (!add_item(b, str, data)) {
225         return tal_free(b);
226     }
227     return b;
228 }
229
230 static struct strgrp_grp *
231 add_grp(struct strgrp *const ctx, const char *const str,
232         void *const data) {
233     struct strgrp_grp *b = new_grp(ctx, str, data);
234     if (!b) {
235         return NULL;
236     }
237     memcpy(b->pop, ctx->pop, sizeof(ctx->pop));
238     darray_push(ctx->grps, b);
239     ctx->n_grps++;
240     if (ctx->scores) {
241         if (!tal_resize(&ctx->scores, ctx->n_grps)) {
242             return NULL;
243         }
244     } else {
245         ctx->scores = tal_arr(ctx, struct grp_score, ctx->n_grps);
246         if (!ctx->scores) {
247             return NULL;
248         }
249     }
250     return b;
251 }
252
253 struct strgrp *
254 strgrp_new(const double threshold) {
255     struct strgrp *ctx = talz(NULL, struct strgrp);
256     ctx->threshold = threshold;
257     stringmap_init(ctx->known, NULL);
258     // n threads compare strings
259     darray_init(ctx->grps);
260     return ctx;
261 }
262
263 static inline void
264 cache(struct strgrp *const ctx, struct strgrp_grp *const grp,
265         const char *const str) {
266     *(stringmap_enter(ctx->known, str)) = grp;
267 }
268
269 static struct strgrp_grp *
270 grp_for(struct strgrp *const ctx, const char *const str) {
271     // Ensure ctx->pop is always populated. Returning null here indicates a new
272     // group should be created, at which point add_grp() copies ctx->pop into
273     // the new group's struct.
274     strpopcnt(str, ctx->pop);
275     if (!ctx->n_grps) {
276         return NULL;
277     }
278     {
279         struct strgrp_grp **const grp = stringmap_lookup(ctx->known, str);
280         if (grp) {
281             return *grp;
282         }
283     }
284     int i;
285 // Keep ccanlint happy in reduced feature mode
286 #if HAVE_OPENMP
287     #pragma omp parallel for schedule(dynamic)
288 #endif
289     for (i = 0; i < ctx->n_grps; i++) {
290         struct strgrp_grp *grp = darray_item(ctx->grps, i);
291         ctx->scores[i].grp = grp;
292         ctx->scores[i].score = 0;
293         if (should_grp_score_len(ctx, grp, str)) {
294             if (should_grp_score_cos(ctx, grp, str)) {
295                 ctx->scores[i].score = grp_score(grp, str);
296             }
297         }
298     }
299     struct grp_score *max = NULL;
300     for (i = 0; i < ctx->n_grps; i++) {
301         if (!max || ctx->scores[i].score > max->score) {
302             max = &(ctx->scores[i]);
303         }
304     }
305     return (max && max->score >= ctx->threshold) ? max->grp : NULL;
306 }
307
308 const struct strgrp_grp *
309 strgrp_grp_for(struct strgrp *const ctx, const char *const str) {
310     return grp_for(ctx, str);
311 }
312
313 const struct strgrp_grp *
314 strgrp_add(struct strgrp *const ctx, const char *const str,
315         void *const data) {
316     bool inserted = false;
317     // grp_for() populates the ctx->pop memory. add_grp() copies this memory
318     // into the strgrp_grp that it creates. It's assumed the ctx->pop memory
319     // has not been modified between the grp_for() and add_grp() calls.
320     struct strgrp_grp *pick = grp_for(ctx, str);
321     if (pick) {
322         inserted = add_item(pick, str, data);
323     } else {
324         pick = add_grp(ctx, str, data);
325         inserted = (NULL != pick);
326     }
327     if (inserted) {
328         assert(NULL != pick);
329         cache(ctx, pick, str);
330     }
331     return pick;
332 }
333
334 struct strgrp_iter *
335 strgrp_iter_new(struct strgrp *const ctx) {
336     struct strgrp_iter *iter = talz(ctx, struct strgrp_iter);
337     if (!iter) {
338         return NULL;
339     }
340     iter->ctx = ctx;
341     iter->i = 0;
342     return iter;
343 }
344
345 const struct strgrp_grp *
346 strgrp_iter_next(struct strgrp_iter *const iter) {
347     return (iter->ctx->n_grps == iter->i) ?
348         NULL : darray_item(iter->ctx->grps, iter->i++);
349 }
350
351 void
352 strgrp_iter_free(struct strgrp_iter *const iter) {
353     tal_free(iter);
354 }
355
356 struct strgrp_grp_iter *
357 strgrp_grp_iter_new(const struct strgrp_grp *const grp) {
358     struct strgrp_grp_iter *iter = talz(grp, struct strgrp_grp_iter);
359     if (!iter) {
360         return NULL;
361     }
362     iter->grp = grp;
363     iter->i = 0;
364     return iter;
365 }
366
367 const struct strgrp_item *
368 strgrp_grp_iter_next(struct strgrp_grp_iter *const iter) {
369     return (iter->grp->n_items == iter->i) ?
370         NULL : darray_item(iter->grp->items, iter->i++);
371 }
372
373 void
374 strgrp_grp_iter_free(struct strgrp_grp_iter *iter) {
375     tal_free(iter);
376 }
377
378 const char *
379 strgrp_grp_key(const struct strgrp_grp *const grp) {
380     return grp->key;
381 }
382
383 const char *
384 strgrp_item_key(const struct strgrp_item *const item) {
385     return item->key;
386 }
387
388 void *
389 strgrp_item_value(const struct strgrp_item *const item) {
390     return item->value;
391 }
392
393 void
394 strgrp_free(struct strgrp *const ctx) {
395     darray_free(ctx->grps);
396     stringmap_free(ctx->known);
397     tal_free(ctx);
398 }
399
400 void
401 strgrp_free_cb(struct strgrp *const ctx, void (*cb)(void *data)) {
402     struct strgrp_grp **grp;
403     struct strgrp_item **item;
404     darray_foreach(grp, ctx->grps) {
405         darray_foreach(item, (*grp)->items) {
406             cb((*item)->value);
407         }
408     }
409     strgrp_free(ctx);
410 }
411
412 static void
413 print_item(const struct strgrp_item *item) {
414     printf("\t%s\n", item->key);
415 }
416
417 static void
418 print_grp(const struct strgrp_grp *const grp) {
419     struct strgrp_item **item;
420     printf("%s:\n", grp->key);
421     darray_foreach(item, grp->items) {
422         print_item(*item);
423     }
424     printf("\n");
425 }
426
427 void
428 strgrp_print(const struct strgrp *const ctx) {
429     struct strgrp_grp **grp;
430     darray_foreach(grp, ctx->grps) {
431         print_grp(*grp);
432     }
433 }