- Added new dir for the library
[textus:php-language-detection.git] / textcat / ngrams.c
1 /*
2    +----------------------------------------------------------------------+
3    | Copyright (c) 2010 The PHP Group                                     |
4    +----------------------------------------------------------------------+
5    | This source file is subject to version 3.01 of the PHP license,      |
6    | that is bundled with this package in the file LICENSE, and is        |
7    | available through the world-wide-web at the following url:           |
8    | http://www.php.net/license/3_01.txt                                  |
9    | If you did not receive a copy of the PHP license and are unable to   |
10    | obtain it through the world-wide-web, please send a note to          |
11    | license@php.net so we can mail you a copy immediately.               |
12    +----------------------------------------------------------------------+
13    | Author:  C├ęsar Rodas <crodas@member.fsf.org>                         |
14    +----------------------------------------------------------------------+
15  */
16
17 #include "textcat.h"
18 #include "textcat_internal.h"
19
20 /* simple_hash(const uchar *, int) {{{ */
21 long textcat_simple_hash(const uchar *str, size_t len, size_t max_number)
22 {
23         long hash = len * 13;
24         while (--len > 0) {
25                 hash = (hash<<5)-hash + *str++;
26         }
27         return (long)hash & max_number;
28 }
29 /* }}} */
30
31 /* textcat_find_ngram(const ngram_set *, const uchar *, int, ngram **) {{{ */
32 Bool textcat_ngram_find(const ngram_set * nset, const uchar * key, size_t len, ngram_t ** item)
33 {
34     ngram_t * entry;
35    
36     for (entry = nset->first; entry!=NULL; entry = entry->next) 
37     {
38         if (entry->len == len && strncmp(entry->str, key, len) == 0) {
39             *item = entry;
40             return TC_TRUE;
41         }
42     }
43     return TC_FALSE;
44 }
45 /* }}} */
46  
47 /* textcat_ngram_incr(TextCat *, ngram_set *, const uchar *, size_t) {{{ */
48 Bool textcat_ngram_incr_ex(TextCat * tc, const uchar * key, size_t len, long freq)
49 {
50     ngram_t * item;
51     ngram_set * nset;
52     int spot;
53     spot = textcat_simple_hash(key, len, tc->hash_size - 1);
54     nset = &(tc->hash.table[spot]);
55     if (textcat_ngram_find(nset, key, len, &item) == TC_TRUE) {
56         item->freq += freq;
57     } else {
58         if (textcat_ngram_create(tc, nset, key, len, &item)  == TC_FALSE) {
59             return TC_FALSE;
60         }
61         item->freq += freq;
62     }
63     return TC_TRUE;
64 }
65
66 Bool textcat_ngram_incr(TextCat * tc, const uchar * key, size_t len)
67 {
68     return textcat_ngram_incr_ex(tc, key, len, 1);
69 }
70 /* }}} */
71
72 /* textcat_ngram_create(TextCat *, ngram_set *, const uchar *, int, ngram **) {{{ */
73 Bool textcat_ngram_create(TextCat * tc, ngram_set * nset, const uchar * key, size_t len, ngram_t ** ritem)
74 {
75     ngram_t * item;
76     item = mempool_malloc(tc->temp, sizeof(ngram_t));
77     CHECK_MEM(item)
78
79     /* setup the new N-gram */
80     item->str  = mempool_strndup(tc->temp, key, len);
81     item->freq = 0;
82     item->len  = len;
83     item->next = NULL;
84
85     CHECK_MEM(item->str);
86     
87     if (nset->first == NULL) {
88         nset->first = item;
89     }
90     if (nset->last != NULL) {
91         nset->last->next = item;
92     }
93     *ritem = item;
94     nset->last = item;
95     nset->total++;
96     tc->hash.ngrams++;
97
98     return TC_TRUE;
99
100 /* }}} */
101
102 /* textcat_init_hash(TextCat * tc) {{{ */
103 Bool textcat_init_hash(TextCat * tc)
104 {
105     ngram_set * table;
106     int i;
107
108     INIT_MEMORY(temp);
109
110     table = mempool_calloc(tc->temp, tc->hash_size, sizeof(ngram_set));
111
112     CHECK_MEM(table)
113
114     for (i=0; i < tc->hash_size; i++) {
115         table[i].first = NULL;
116         table[i].last  = NULL;
117         table[i].total = 0;
118     }
119
120     tc->hash.table  = table;
121     tc->hash.ngrams = 0;
122     tc->hash.size   = tc->hash_size;
123     return TC_TRUE; 
124 }
125 /* }}} */
126
127 /* textcat_destroy_hash(TextCat * tc)  {{{ */
128 void textcat_destroy_hash(TextCat * tc) 
129 {
130     mempool_reset(tc->temp);
131 }
132 /* }}} */
133
134 /* textcat_copy_result(TextCat * tc, NGrams ** result) {{{ */
135 static int textcat_hash_sort(const void * a, const void *b)
136 {
137     int diff = 0;
138     ngram_t *aa, *bb;
139     aa = *(ngram_t **) a;
140     bb = *(ngram_t **) b;
141     diff = bb->freq - aa->freq;
142     /* if they have the same frequency, let's order 
143      * by string, in a descendent fashion
144      */
145     if (diff == 0) {
146         diff = strcmp(bb->str, aa->str);
147     }
148     return diff;
149 }
150
151 Bool textcat_copy_result(TextCat * tc, NGrams ** result)
152 {
153     NGrams * ngrams;
154     ngram_t * entry, **temp;
155     long i, e;
156     long length;
157
158     temp = (ngram_t **) mempool_malloc(tc->temp, sizeof(ngram_t *) * tc->hash.ngrams);
159     CHECK_MEM(temp);
160
161     for (i=0, e=0; i < tc->hash.size; i++) {
162         for (entry = tc->hash.table[i].first; entry ; entry = entry->next) {
163             *(temp+e) = entry;
164             e++;
165         }
166     }
167
168     /* simple checking */
169     assert(e == tc->hash.ngrams);
170
171     /* sort by the hash by frequency */
172     qsort(temp, tc->hash.ngrams, sizeof(ngram_t *), &textcat_hash_sort);
173
174     /* guess the number of desires N-grams */
175     length = tc->hash.ngrams > tc->max_ngrams ?  tc->max_ngrams : tc->hash.ngrams;
176
177     /* preparing the result */
178     ngrams = (NGrams *) mempool_malloc(tc->memory, sizeof(NGrams));
179     CHECK_MEM(ngrams);
180     ngrams->ngram = (NGram *) mempool_calloc(tc->memory, length, sizeof(NGram));
181     CHECK_MEM(ngrams->ngram);
182     ngrams->size = length;
183
184     /* copying the first 'length' ngrams */
185     for (i=0; i < length; i++) {
186         ngrams->ngram[i].str      = mempool_strndup(tc->memory,temp[i]->str, temp[i]->len);
187         ngrams->ngram[i].freq     = temp[i]->freq;
188         ngrams->ngram[i].size     = temp[i]->len;
189         ngrams->ngram[i].position = i;
190         CHECK_MEM(ngrams->ngram[i].str);
191     }
192
193     /* sort by string (for fast comparition) */
194     textcat_ngram_sort_by_str(ngrams);
195
196     *result = ngrams;
197
198     return TC_TRUE;
199 }
200 /* }}} */
201
202 /* Sorting {{{ */
203 static int textcat_qsort_fnc_freq(const void * a, const void * b)
204 {   
205     int diff;
206     NGram *aa, *bb;
207     aa = (NGram *) a;
208     bb = (NGram *) b;
209     diff = bb->freq - aa->freq;
210     /* if they have the same frequency, let's order 
211      * by string, in a descendent fashion
212      */
213     if (diff == 0) {
214         diff = strcmp(bb->str, aa->str);
215     }
216     return diff;
217 }
218
219 static int textcat_qsort_fnc_str(const void * a, const void * b)
220 {
221     NGram *aa, *bb;
222     aa = (NGram *) a;
223     bb = (NGram *) b;
224     return strcmp(aa->str, bb->str);
225 }
226
227 void textcat_ngram_sort_by_freq(NGrams * ngrams)
228 {
229     qsort(ngrams->ngram, ngrams->size, sizeof(NGram), textcat_qsort_fnc_freq);
230 }
231
232 void textcat_ngram_sort_by_str(NGrams * ngrams)
233 {
234     qsort(ngrams->ngram, ngrams->size, sizeof(NGram), textcat_qsort_fnc_str);
235 }
236
237 /* }}} */
238
239 /* textcat_result_merge(TextCat *, result_stack *, NGrams ** ) {{{ */
240 Bool textcat_result_merge(TextCat *tc, result_stack * stack, NGrams ** result)
241 {
242     NGram * tmp;
243     long i;
244     if (textcat_init_hash(tc) == TC_FALSE) {
245         return TC_FALSE;
246     }
247     while (stack) {
248         for (i=0; i < stack->result->size; i++) {
249             tmp = &stack->result->ngram[i];
250             if (textcat_ngram_incr_ex(tc, tmp->str, tmp->size, tmp->freq) == TC_FALSE) {
251                 textcat_destroy_hash(tc);
252                 return TC_FALSE;
253             }
254         }
255         stack = stack->next;
256     }
257     if (textcat_copy_result(tc, result) == TC_FALSE) {
258         textcat_destroy_hash(tc);
259         return TC_FALSE;
260     }
261     textcat_destroy_hash(tc);
262     return TC_TRUE;
263 }
264 /* }}} */
265
266 void ngrams_print(NGrams * ng)
267 {
268     int i;
269     for (i=0; i < ng->size; i++) {
270         printf("[%s] %d %d\n", ng->ngram[i].str, ng->ngram[i].position, ng->ngram[i].freq);
271     }
272 }
273
274 /*
275  * Local variables:
276  * tab-width: 4
277  * c-basic-offset: 4
278  * End:
279  * vim600: sw=4 ts=4 fdm=marker
280  * vim<600: sw=4 ts=4
281  */