a1251cb74cbfd816c7dab2ed54129659a0215e0b
[pspp-builds.git] / src / data / case-matcher.c
1 /* PSPP - a program for statistical analysis.
2    Copyright (C) 2008, 2009 Free Software Foundation, Inc.
3
4    This program is free software: you can redistribute it and/or modify
5    it under the terms of the GNU General Public License as published by
6    the Free Software Foundation, either version 3 of the License, or
7    (at your option) any later version.
8
9    This program is distributed in the hope that it will be useful,
10    but WITHOUT ANY WARRANTY; without even the implied warranty of
11    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12    GNU General Public License for more details.
13
14    You should have received a copy of the GNU General Public License
15    along with this program.  If not, see <http://www.gnu.org/licenses/>. */
16
17 #include <config.h>
18
19 #include <data/case-matcher.h>
20
21 #include <stdlib.h>
22
23 #include <data/case.h>
24 #include <data/subcase.h>
25 #include <data/value.h>
26 #include <libpspp/assertion.h>
27
28 #include "xalloc.h"
29
30 struct case_matcher_input
31   {
32     struct subcase by_vars;
33     struct ccase **data;
34     bool *is_minimal;
35   };
36
37 struct case_matcher
38   {
39     struct case_matcher_input *inputs;
40     size_t n_inputs, allocated_inputs;
41     union value *by_values;
42   };
43
44 /* Creates and returns a new case matcher. */
45 struct case_matcher *
46 case_matcher_create (void)
47 {
48   struct case_matcher *cm = xmalloc (sizeof *cm);
49   cm->inputs = NULL;
50   cm->n_inputs = 0;
51   cm->allocated_inputs = 0;
52   cm->by_values = NULL;
53   return cm;
54 }
55
56 /* Adds a new input file to case matcher CM.
57    case_matcher_match() will compare the variables specified in
58    BY in case *DATA and set *IS_MINIMAL appropriately.
59    (The caller may change the case that *DATA points to from one
60    call to the next.)
61
62    All of the BY subcases provided to this function for a given
63    CM must be conformable (see subcase_conformable()). */
64 void
65 case_matcher_add_input (struct case_matcher *cm, const struct subcase *by,
66                         struct ccase **data, bool *is_minimal)
67 {
68   struct case_matcher_input *input;
69
70   if (cm->n_inputs == 0)
71     cm->by_values = xmalloc (subcase_get_n_values (by)
72                              * sizeof *cm->by_values);
73   else
74     assert (subcase_conformable (by, &cm->inputs[0].by_vars));
75
76   if (cm->n_inputs >= cm->allocated_inputs)
77     cm->inputs = x2nrealloc (cm->inputs, &cm->allocated_inputs,
78                              sizeof *cm->inputs);
79   input = &cm->inputs[cm->n_inputs++];
80   subcase_clone (&input->by_vars, by);
81   input->data = data;
82   input->is_minimal = is_minimal;
83 }
84
85 /* Destroys case matcher CM. */
86 void
87 case_matcher_destroy (struct case_matcher *cm)
88 {
89   if (cm != NULL)
90     {
91       size_t i;
92
93       for (i = 0; i < cm->n_inputs; i++)
94         {
95           struct case_matcher_input *input = &cm->inputs[i];
96           subcase_destroy (&input->by_vars);
97         }
98       free (cm->inputs);
99       free (cm);
100     }
101 }
102
103 static int
104 compare_BY_3way (struct case_matcher_input *a, struct case_matcher_input *b)
105 {
106   return subcase_compare_3way (&a->by_vars, *a->data, &b->by_vars, *b->data);
107 }
108
109 /* Compares the values of the BY variables in all of the nonnull
110    cases provided to case_matcher_add_input() for CM, sets
111    *IS_MINIMAL for each one to true if it has the minimum BY
112    values among those cases or to false if its BY values are
113    greater than the minimum.  Also sets *IS_MINIMAL to false for
114    null cases.  Sets *BY to the BY values extracted from the
115    minimum case.  (The caller must not free *BY.)
116
117    Returns true if at least one of the cases is nonnull, false
118    if they are all null.*/
119 bool
120 case_matcher_match (struct case_matcher *cm, union value **by)
121 {
122   struct case_matcher_input *file, *min;
123
124   min = NULL;
125   for (file = cm->inputs; file < &cm->inputs[cm->n_inputs]; file++)
126     if (*file->data != NULL)
127       {
128         int cmp = min != NULL ? compare_BY_3way (min, file) : 1;
129         if (cmp < 0)
130           *file->is_minimal = false;
131         else
132           {
133             *file->is_minimal = true;
134             if (cmp > 0)
135               min = file;
136           }
137       }
138     else
139       *file->is_minimal = false;
140
141   if (min != NULL)
142     {
143       for (file = cm->inputs; file < min; file++)
144         *file->is_minimal = false;
145       subcase_extract (&min->by_vars, *min->data, cm->by_values);
146       *by = cm->by_values;
147       return true;
148     }
149   else
150     {
151       *by = NULL;
152       return false;
153     }
154 }