1 /* This file is part of Pazpar2.
2 Copyright (C) 2006-2009 Index Data
4 Pazpar2 is free software; you can redistribute it and/or modify it under
5 the terms of the GNU General Public License as published by the Free
6 Software Foundation; either version 2, or (at your option) any later
9 Pazpar2 is distributed in the hope that it will be useful, but WITHOUT ANY
10 WARRANTY; without even the implied warranty of MERCHANTABILITY or
11 FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
14 You should have received a copy of the GNU General Public License
15 along with this program; if not, write to the Free Software
16 Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
27 #include "relevance.h"
34 int *doc_frequency_vec;
39 struct word_entry *entries;
46 #define raw_char(c) (((c) >= 'a' && (c) <= 'z') ? (c) - 'a' : -1)
49 // We use this data structure to recognize terms in input records,
50 // and map them to record term vectors for counting.
55 struct word_trie *child;
60 static struct word_trie *create_word_trie_node(NMEM nmem)
62 struct word_trie *res = nmem_malloc(nmem, sizeof(struct word_trie));
64 for (i = 0; i < 26; i++)
66 res->list[i].child = 0;
67 res->list[i].termno = -1;
72 static void word_trie_addterm(NMEM nmem, struct word_trie *n, const char *term, int num)
76 int c = tolower(*term);
77 if (c < 'a' || c > 'z')
83 n->list[c].termno = num;
86 if (!n->list[c].child)
88 struct word_trie *new = create_word_trie_node(nmem);
89 n->list[c].child = new;
91 word_trie_addterm(nmem, n->list[c].child, term, num);
98 static int word_trie_match(struct word_trie *t, const char *word, int *skipped)
100 int c = raw_char(tolower(*word));
107 if (!*word || raw_char(*word) < 0)
109 if (t->list[c].termno > 0)
110 return t->list[c].termno;
116 if (t->list[c].child)
118 return word_trie_match(t->list[c].child, word, skipped);
127 static struct word_trie *build_word_trie(NMEM nmem, const char **terms)
129 struct word_trie *res = create_word_trie_node(nmem);
133 for (i = 1, p = terms; *p; p++, i++)
134 word_trie_addterm(nmem, res, *p, i);
139 // FIXME. The definition of a word is crude here.. should support
140 // some form of localization mechanism?
141 void relevance_countwords(struct relevance *r, struct record_cluster *cluster,
142 const char *words, int multiplier)
149 while (*words && (c = raw_char(tolower(*words))) < 0)
153 res = word_trie_match(r->wt, words, &skipped);
157 cluster->term_frequency_vec[res] += multiplier;
161 while (*words && (c = raw_char(tolower(*words))) >= 0)
164 cluster->term_frequency_vec[0]++;
171 const char *norm_str;
173 struct word_entry *next;
176 static void add_word_entry(NMEM nmem,
177 struct word_entry **entries,
178 const char *norm_str,
181 struct word_entry *ne = nmem_malloc(nmem, sizeof(*ne));
182 ne->norm_str = nmem_strdup(nmem, norm_str);
183 ne->termno = term_no;
190 int word_entry_match(struct word_entry *entries, const char *norm_str)
192 for (; entries; entries = entries->next)
194 if (!strcmp(norm_str, entries->norm_str))
195 return entries->termno;
200 static struct word_entry *build_word_entries(pp2_charset_t pct, NMEM nmem,
203 int termno = 1; /* >0 signals THERE is an entry */
204 struct word_entry *entries = 0;
205 const char **p = terms;
209 pp2_relevance_token_t prt = pp2_relevance_tokenize(pct, *p);
210 const char *norm_str;
212 while ((norm_str = pp2_relevance_token_next(prt)))
213 add_word_entry(nmem, &entries, norm_str, termno);
215 pp2_relevance_token_destroy(prt);
222 void relevance_countwords(struct relevance *r, struct record_cluster *cluster,
223 const char *words, int multiplier)
225 pp2_relevance_token_t prt = pp2_relevance_tokenize(r->pct, words);
227 const char *norm_str;
229 while ((norm_str = pp2_relevance_token_next(prt)))
231 int res = word_entry_match(r->entries, norm_str);
233 cluster->term_frequency_vec[res] += multiplier;
234 cluster->term_frequency_vec[0]++;
236 pp2_relevance_token_destroy(prt);
243 struct relevance *relevance_create(pp2_charset_t pct,
244 NMEM nmem, const char **terms, int numrecs)
246 struct relevance *res = nmem_malloc(nmem, sizeof(struct relevance));
250 for (p = terms, i = 0; *p; p++, i++)
253 res->doc_frequency_vec = nmem_malloc(nmem, res->vec_len * sizeof(int));
254 memset(res->doc_frequency_vec, 0, res->vec_len * sizeof(int));
257 res->wt = build_word_trie(nmem, terms);
259 res->entries = build_word_entries(pct, nmem, terms);
265 void relevance_newrec(struct relevance *r, struct record_cluster *rec)
267 if (!rec->term_frequency_vec)
269 rec->term_frequency_vec = nmem_malloc(r->nmem, r->vec_len * sizeof(int));
270 memset(rec->term_frequency_vec, 0, r->vec_len * sizeof(int));
275 void relevance_donerecord(struct relevance *r, struct record_cluster *cluster)
279 for (i = 1; i < r->vec_len; i++)
280 if (cluster->term_frequency_vec[i] > 0)
281 r->doc_frequency_vec[i]++;
283 r->doc_frequency_vec[0]++;
286 // Prepare for a relevance-sorted read
287 void relevance_prepare_read(struct relevance *rel, struct reclist *reclist)
290 float *idfvec = xmalloc(rel->vec_len * sizeof(float));
292 // Calculate document frequency vector for each term.
293 for (i = 1; i < rel->vec_len; i++)
295 if (!rel->doc_frequency_vec[i])
299 // This conditional may be terribly wrong
300 // It was there to address the situation where vec[0] == vec[i]
301 // which leads to idfvec[i] == 0... not sure about this
302 // Traditional TF-IDF may assume that a word that occurs in every
303 // record is irrelevant, but this is actually something we will
305 if ((idfvec[i] = log((float) rel->doc_frequency_vec[0] /
306 rel->doc_frequency_vec[i])) < 0.0000001)
310 // Calculate relevance for each document
311 for (i = 0; i < reclist_get_num_records(reclist); i++)
314 struct record_cluster *rec = reclist_get_cluster(reclist, i);
317 for (t = 1; t < rel->vec_len; t++)
320 if (!rec->term_frequency_vec[0])
322 termfreq = (float) rec->term_frequency_vec[t] / rec->term_frequency_vec[0];
323 relevance += termfreq * idfvec[t];
325 rec->relevance = (int) (relevance * 100000);
327 reclist_rewind(reclist);
334 * c-file-style: "Stroustrup"
335 * indent-tabs-mode: nil
337 * vim: shiftwidth=4 tabstop=8 expandtab