Metadata 'skiparticle works for ICU normalization
[pazpar2-moved-to-github.git] / src / relevance.c
index 4597c67..0234d91 100644 (file)
@@ -1,8 +1,27 @@
-/*
- * $Id: relevance.c,v 1.2 2006-12-20 22:18:33 adam Exp $
- */
+/* This file is part of Pazpar2.
+   Copyright (C) 2006-2009 Index Data
+
+Pazpar2 is free software; you can redistribute it and/or modify it under
+the terms of the GNU General Public License as published by the Free
+Software Foundation; either version 2, or (at your option) any later
+version.
+
+Pazpar2 is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 
-#include <ctype.h>
+*/
+
+#if HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <assert.h>
 #include <math.h>
 #include <stdlib.h>
 
@@ -13,101 +32,99 @@ struct relevance
 {
     int *doc_frequency_vec;
     int vec_len;
-    struct word_trie *wt;
+    struct word_entry *entries;
+    pp2_charset_t pct;
     NMEM nmem;
 };
 
-// We use this data structure to recognize terms in input records,
-// and map them to record term vectors for counting.
-struct word_trie
-{
-    struct
-    {
-        struct word_trie *child;
-        int termno;
-    } list[26];
+
+struct word_entry {
+    const char *norm_str;
+    int termno;
+    struct word_entry *next;
 };
 
-static struct word_trie *create_word_trie_node(NMEM nmem)
+static void add_word_entry(NMEM nmem, 
+                           struct word_entry **entries,
+                           const char *norm_str,
+                           int term_no)
 {
-    struct word_trie *res = nmem_malloc(nmem, sizeof(struct word_trie));
-    int i;
-    for (i = 0; i < 26; i++)
+    struct word_entry *ne = nmem_malloc(nmem, sizeof(*ne));
+    ne->norm_str = nmem_strdup(nmem, norm_str);
+    ne->termno = term_no;
+    
+    ne->next = *entries;
+    *entries = ne;
+}
+
+
+int word_entry_match(struct word_entry *entries, const char *norm_str)
+{
+    for (; entries; entries = entries->next)
     {
-        res->list[i].child = 0;
-        res->list[i].termno = -1;
+        if (!strcmp(norm_str, entries->norm_str))
+            return entries->termno;
     }
-    return res;
+    return 0;
 }
 
-static void word_trie_addterm(NMEM nmem, struct word_trie *n, const char *term, int num)
+static struct word_entry *build_word_entries(pp2_charset_t pct, NMEM nmem,
+                                             const char **terms)
 {
-    while (*term) {
-        int c = tolower(*term);
-        if (c < 'a' || c > 'z')
-            term++;
-        else
-        {
-            c -= 'a';
-            if (!*(++term))
-                n->list[c].termno = num;
-            else
-            {
-                if (!n->list[c].child)
-                {
-                    struct word_trie *new = create_word_trie_node(nmem);
-                    n->list[c].child = new;
-                }
-                word_trie_addterm(nmem, n->list[c].child, term, num);
-            }
-            break;
-        }
+    int termno = 1; /* >0 signals THERE is an entry */
+    struct word_entry *entries = 0;
+    const char **p = terms;
+
+    for (; *p; p++)
+    {
+        pp2_relevance_token_t prt = pp2_relevance_tokenize(pct, *p, 0);
+        const char *norm_str;
+
+        while ((norm_str = pp2_relevance_token_next(prt)))
+            add_word_entry(nmem, &entries, norm_str, termno);
+
+        pp2_relevance_token_destroy(prt);
+
+        termno++;
     }
+    return entries;
 }
 
-#define raw_char(c) (((c) >= 'a' && (c) <= 'z') ? (c) - 'a' : -1)
-
-static int word_trie_match(struct word_trie *t, const char *word, int len, int *skipped)
+void relevance_countwords(struct relevance *r, struct record_cluster *cluster,
+                          const char *words, int multiplier, const char *name)
 {
-    int c = raw_char(tolower(*word));
+    pp2_relevance_token_t prt = pp2_relevance_tokenize(r->pct, words, 0);
+    int *mult = cluster->term_frequency_vec_tmp;
+    const char *norm_str;
+    int i, length = 0;
 
-    if (!len)
-        return 0;
+    for (i = 1; i < r->vec_len; i++)
+        mult[i] = 0;
 
-    word++; len--;
-    (*skipped)++;
-    if (!len || raw_char(*word) < 0)
-    {
-        if (t->list[c].termno > 0)
-            return t->list[c].termno;
-        else
-            return 0;
-    }
-    else
+    while ((norm_str = pp2_relevance_token_next(prt)))
     {
-        if (t->list[c].child)
+        int res = word_entry_match(r->entries, norm_str);
+        if (res)
         {
-            return word_trie_match(t->list[c].child, word, len, skipped);
+            assert(res < r->vec_len);
+            mult[res] += multiplier;
         }
-        else
-            return 0;
+        length++;
     }
 
-}
-
-
-static struct word_trie *build_word_trie(NMEM nmem, const char **terms)
-{
-    struct word_trie *res = create_word_trie_node(nmem);
-    const char **p;
-    int i;
+    for (i = 1; i < r->vec_len; i++)
+    {
+        if (length > 0) /* only add if non-empty */
+            cluster->term_frequency_vecf[i] += (double) mult[i] / length;
+        cluster->term_frequency_vec[i] += mult[i];
+    }
 
-    for (i = 1, p = terms; *p; p++, i++)
-        word_trie_addterm(nmem, res, *p, i);
-    return res;
+    cluster->term_frequency_vec[0] += length;
+    pp2_relevance_token_destroy(prt);
 }
 
-struct relevance *relevance_create(NMEM nmem, const char **terms, int numrecs)
+struct relevance *relevance_create(pp2_charset_t pct,
+                                   NMEM nmem, const char **terms)
 {
     struct relevance *res = nmem_malloc(nmem, sizeof(struct relevance));
     const char **p;
@@ -117,132 +134,114 @@ struct relevance *relevance_create(NMEM nmem, const char **terms, int numrecs)
         ;
     res->vec_len = ++i;
     res->doc_frequency_vec = nmem_malloc(nmem, res->vec_len * sizeof(int));
-    bzero(res->doc_frequency_vec, res->vec_len * sizeof(int));
+    memset(res->doc_frequency_vec, 0, res->vec_len * sizeof(int));
     res->nmem = nmem;
-    res->wt = build_word_trie(nmem, terms);
+    res->entries = build_word_entries(pct, nmem, terms);
+    res->pct = pct;
     return res;
 }
 
-void relevance_newrec(struct relevance *r, struct record *rec)
+void relevance_newrec(struct relevance *r, struct record_cluster *rec)
 {
     if (!rec->term_frequency_vec)
     {
-        rec->term_frequency_vec = nmem_malloc(r->nmem, r->vec_len * sizeof(int));
-        bzero(rec->term_frequency_vec, r->vec_len * sizeof(int));
+        int i;
+
+        // term frequency [1,..] . [0] is total length of all fields
+        rec->term_frequency_vec =
+            nmem_malloc(r->nmem,
+                        r->vec_len * sizeof(*rec->term_frequency_vec));
+        for (i = 0; i < r->vec_len; i++)
+            rec->term_frequency_vec[i] = 0;
+        
+        // term frequency divided by length of field [1,...]
+        rec->term_frequency_vecf =
+            nmem_malloc(r->nmem,
+                        r->vec_len * sizeof(*rec->term_frequency_vecf));
+        for (i = 0; i < r->vec_len; i++)
+            rec->term_frequency_vecf[i] = 0.0;
+        
+        // for relevance_countwords (so we don't have to xmalloc/xfree)
+        rec->term_frequency_vec_tmp =
+            nmem_malloc(r->nmem,
+                        r->vec_len * sizeof(*rec->term_frequency_vec_tmp));
     }
 }
 
 
-// FIXME. The definition of a word is crude here.. should support
-// some form of localization mechanism?
-void relevance_countwords(struct relevance *r, struct record *head,
-        const char *words, int len, int multiplier)
-{
-    while (len)
-    {
-        char c;
-        int res;
-        int skipped;
-        while (len && (c = raw_char(tolower(*words))) < 0)
-        {
-            words++;
-            len--;
-        }
-        if (!len)
-            return;
-        skipped = 0;
-        if ((res = word_trie_match(r->wt, words, len, &skipped)))
-        {
-            words += skipped;
-            len -= skipped;
-            head->term_frequency_vec[res] += multiplier;
-        }
-        else
-        {
-            while (len && (c = raw_char(tolower(*words))) >= 0)
-            {
-                words++;
-                len--;
-            }
-        }
-        head->term_frequency_vec[0]++;
-    }
-}
-
-void relevance_donerecord(struct relevance *r, struct record *head)
+void relevance_donerecord(struct relevance *r, struct record_cluster *cluster)
 {
     int i;
 
     for (i = 1; i < r->vec_len; i++)
-        if (head->term_frequency_vec[i] > 0)
+        if (cluster->term_frequency_vec[i] > 0)
             r->doc_frequency_vec[i]++;
 
     r->doc_frequency_vec[0]++;
 }
 
-#ifdef FLOAT_REL
-static int comp(const void *p1, const void *p2)
-{
-    float res;
-    struct record **r1 = (struct record **) p1;
-    struct record **r2 = (struct record **) p2;
-    res = (*r2)->relevance - (*r1)->relevance;
-    if (res > 0)
-        return 1;
-    else if (res < 0)
-        return -1;
-    else
-        return 0;
-}
-#else
-static int comp(const void *p1, const void *p2)
-{
-    struct record **r1 = (struct record **) p1;
-    struct record **r2 = (struct record **) p2;
-    return (*r2)->relevance - (*r1)->relevance;
-}
-#endif
-
-// Prepare for a relevance-sorted read of up to num entries
+// Prepare for a relevance-sorted read
 void relevance_prepare_read(struct relevance *rel, struct reclist *reclist)
 {
     int i;
     float *idfvec = xmalloc(rel->vec_len * sizeof(float));
 
+    reclist_rewind(reclist);
     // Calculate document frequency vector for each term.
     for (i = 1; i < rel->vec_len; i++)
     {
         if (!rel->doc_frequency_vec[i])
             idfvec[i] = 0;
         else
-            idfvec[i] = log((float) rel->doc_frequency_vec[0] / rel->doc_frequency_vec[i]);
+        {
+            // This conditional may be terribly wrong
+            // It was there to address the situation where vec[0] == vec[i]
+            // which leads to idfvec[i] == 0... not sure about this
+            // Traditional TF-IDF may assume that a word that occurs in every
+            // record is irrelevant, but this is actually something we will
+            // see a lot
+            if ((idfvec[i] = log((float) rel->doc_frequency_vec[0] /
+                            rel->doc_frequency_vec[i])) < 0.0000001)
+                idfvec[i] = 1;
+        }
     }
     // Calculate relevance for each document
-    for (i = 0; i < reclist->num_records; i++)
+
+    while (1)
     {
         int t;
-        struct record *rec = reclist->flatlist[i];
-        float relevance;
-        relevance = 0;
+        int relevance = 0;
+        struct record_cluster *rec = reclist_read_record(reclist);
+        if (!rec)
+            break;
         for (t = 1; t < rel->vec_len; t++)
         {
             float termfreq;
-            if (!rec->term_frequency_vec[0])
-                break;
-            termfreq = (float) rec->term_frequency_vec[t] / rec->term_frequency_vec[0];
-            relevance += termfreq * idfvec[t];
+#if 1
+            termfreq = (float) rec->term_frequency_vecf[t];
+#else
+            if (rec->term_frequency_vec[0])
+            {
+                termfreq = (float)
+                    rec->term_frequency_vec[t] / rec->term_frequency_vec[0] ;
+            }
+            else
+                termfreq = 0.0;
+#endif
+            relevance += 100000 * (termfreq * idfvec[t] + 0.0000005);  
         }
-        rec->relevance = (int) (relevance * 100000);
+        rec->relevance = relevance;
     }
-    qsort(reclist->flatlist, reclist->num_records, sizeof(struct record*), comp);
-    reclist->pointer = 0;
+    reclist_rewind(reclist);
     xfree(idfvec);
 }
 
 /*
  * Local variables:
  * c-basic-offset: 4
+ * c-file-style: "Stroustrup"
  * indent-tabs-mode: nil
  * End:
  * vim: shiftwidth=4 tabstop=8 expandtab
  */
+