9cfc0083c2b2b4ff624989cd2b42097ddd4b5f7e
[pspp-builds.git] / src / data / case-matcher.c
1 /* PSPP - a program for statistical analysis.
2    Copyright (C) 2008 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     const 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
60    All of the BY subcases provided to this function for a given
61    CM must be conformable (see subcase_conformable()). */
62 void
63 case_matcher_add_input (struct case_matcher *cm, const struct subcase *by,
64                         const struct ccase *data, bool *is_minimal)
65 {
66   struct case_matcher_input *input;
67
68   if (cm->n_inputs == 0)
69     cm->by_values = xmalloc (subcase_get_n_values (by)
70                              * sizeof *cm->by_values);
71   else
72     assert (subcase_conformable (by, &cm->inputs[0].by_vars));
73
74   if (cm->n_inputs >= cm->allocated_inputs)
75     cm->inputs = x2nrealloc (cm->inputs, &cm->allocated_inputs,
76                              sizeof *cm->inputs);
77   input = &cm->inputs[cm->n_inputs++];
78   subcase_clone (&input->by_vars, by);
79   input->data = data;
80   input->is_minimal = is_minimal;
81 }
82
83 /* Destroys case matcher CM. */
84 void
85 case_matcher_destroy (struct case_matcher *cm)
86 {
87   if (cm != NULL)
88     {
89       size_t i;
90
91       for (i = 0; i < cm->n_inputs; i++)
92         {
93           struct case_matcher_input *input = &cm->inputs[i];
94           subcase_destroy (&input->by_vars);
95         }
96       free (cm->inputs);
97       free (cm);
98     }
99 }
100
101 static int
102 compare_BY_3way (struct case_matcher_input *a, struct case_matcher_input *b)
103 {
104   return subcase_compare_3way (&a->by_vars, a->data, &b->by_vars, b->data);
105 }
106
107 /* Compares the values of the BY variables in all of the nonnull
108    cases provided to case_matcher_add_input() for CM, sets
109    *IS_MINIMAL for each one to true if it has the minimum BY
110    values among those cases or to false if its BY values are
111    greater than the minimum.  Also sets *IS_MINIMAL to false for
112    null cases.  Sets *BY to the BY values extracted from the
113    minimum case.  (The caller must not free *BY.)
114
115    Returns true if at least one of the cases is nonnull, false
116    if they are all null.*/
117 bool
118 case_matcher_match (struct case_matcher *cm, union value **by)
119 {
120   struct case_matcher_input *file, *min;
121
122   min = NULL;
123   for (file = cm->inputs; file < &cm->inputs[cm->n_inputs]; file++)
124     if (!case_is_null (file->data))
125       {
126         int cmp = min != NULL ? compare_BY_3way (min, file) : 1;
127         if (cmp < 0)
128           *file->is_minimal = false;
129         else
130           {
131             *file->is_minimal = true;
132             if (cmp > 0)
133               min = file;
134           }
135       }
136     else
137       *file->is_minimal = false;
138
139   if (min != NULL)
140     {
141       for (file = cm->inputs; file < min; file++)
142         *file->is_minimal = false;
143       subcase_extract (&min->by_vars, min->data, cm->by_values);
144       *by = cm->by_values;
145       return true;
146     }
147   else
148     {
149       *by = NULL;
150       return false;
151     }
152 }