SORT VARIABLES: Improve stability of sort.
[pspp] / src / language / dictionary / sort-variables.c
1 /* PSPP - a program for statistical analysis.
2    Copyright (C) 2016 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 <stdlib.h>
20
21 #include "data/attributes.h"
22 #include "data/dataset.h"
23 #include "data/dictionary.h"
24 #include "data/format.h"
25 #include "data/variable.h"
26 #include "language/command.h"
27 #include "language/lexer/lexer.h"
28 #include "libpspp/array.h"
29 #include "libpspp/assertion.h"
30 #include "libpspp/i18n.h"
31 #include "libpspp/message.h"
32 #include "libpspp/str.h"
33
34 #include "gl/xalloc.h"
35
36 #include "gettext.h"
37 #define _(msgid) gettext (msgid)
38
39 enum key
40   {
41     K_NAME,
42     K_TYPE,
43     K_FORMAT,
44     K_VAR_LABEL,
45     K_VALUE_LABELS,
46     K_MISSING_VALUES,
47     K_MEASURE,
48     K_ROLE,
49     K_COLUMNS,
50     K_ALIGNMENT,
51     K_ATTRIBUTE,
52   };
53
54 struct criterion
55   {
56     enum key key;
57     char *attr_name;
58     bool descending;
59   };
60
61 static int
62 compare_ints (int a, int b)
63 {
64   return a < b ? -1 : a > b;
65 }
66
67 static int
68 compare_formats (const struct fmt_spec *a, const struct fmt_spec *b)
69 {
70   int retval = compare_ints (fmt_to_io (a->type), fmt_to_io (b->type));
71   if (!retval)
72     retval = compare_ints (a->w, b->w);
73   if (!retval)
74     retval = compare_ints (a->d, b->d);
75   return retval;
76 }
77
78 static int
79 compare_var_labels (const struct variable *a, const struct variable *b)
80 {
81   const char *a_label = var_get_label (a);
82   const char *b_label = var_get_label (b);
83   return utf8_strcasecmp (a_label ? a_label : "",
84                           b_label ? b_label : "");
85 }
86
87 static int
88 map_measure (enum measure m)
89 {
90   return (m == MEASURE_NOMINAL ? 0
91           : m == MEASURE_ORDINAL ? 1
92           : 2);
93 }
94
95 static int
96 map_role (enum var_role r)
97 {
98   return (r == ROLE_INPUT ? 0
99           : r == ROLE_TARGET ? 1
100           : r == ROLE_BOTH ? 2
101           : r == ROLE_NONE ? 3
102           : r == ROLE_PARTITION ? 4
103           : 5);
104 }
105
106 static const char *
107 get_attribute (const struct variable *v, const char *name)
108 {
109   const struct attrset *set = var_get_attributes (v);
110   const struct attribute *attr = attrset_lookup (set, name);
111   const char *value = attr ? attribute_get_value (attr, 0) : NULL;
112   return value ? value : "";
113 }
114
115 static int
116 map_alignment (enum alignment a)
117 {
118   return (a == ALIGN_LEFT ? 0
119           : a == ALIGN_RIGHT ? 1
120           : 2);
121 }
122
123 static int
124 compare_vars (const void *a_, const void *b_, const void *c_)
125 {
126   const struct variable *const *ap = a_;
127   const struct variable *const *bp = b_;
128   const struct variable *a = *ap;
129   const struct variable *b = *bp;
130   const struct criterion *c = c_;
131
132   int retval;
133   switch (c->key)
134     {
135     case K_NAME:
136       retval = utf8_strverscasecmp (var_get_name (a), var_get_name (b));
137       break;
138
139     case K_TYPE:
140       retval = compare_ints (var_get_width (a), var_get_width (b));
141       break;
142
143     case K_FORMAT:
144       retval = compare_formats (var_get_print_format (a),
145                                 var_get_print_format (b));
146       break;
147
148     case K_VAR_LABEL:
149       retval = compare_var_labels (a, b);
150       break;
151
152     case K_VALUE_LABELS:
153       retval = compare_ints (var_has_value_labels (a),
154                              var_has_value_labels (b));
155       break;
156
157     case K_MISSING_VALUES:
158       retval = compare_ints (var_has_missing_values (a),
159                              var_has_missing_values (b));
160       break;
161
162     case K_MEASURE:
163       retval = compare_ints (map_measure (var_get_measure (a)),
164                              map_measure (var_get_measure (b)));
165       break;
166
167     case K_ROLE:
168       retval = compare_ints (map_role (var_get_role (a)),
169                              map_role (var_get_role (b)));
170       break;
171
172     case K_COLUMNS:
173       retval = compare_ints (var_get_display_width (a),
174                              var_get_display_width (b));
175       break;
176
177     case K_ALIGNMENT:
178       retval = compare_ints (map_alignment (var_get_alignment (a)),
179                              map_alignment (var_get_alignment (b)));
180       break;
181
182     case K_ATTRIBUTE:
183       retval = utf8_strcasecmp (get_attribute (a, c->attr_name),
184                                 get_attribute (b, c->attr_name));
185       break;
186
187     default:
188       NOT_REACHED ();
189     }
190
191   /* Make this a stable sort. */
192   if (!retval)
193     {
194       size_t a_index = var_get_dict_index (a);
195       size_t b_index = var_get_dict_index (b);
196       retval = a_index < b_index ? -1 : a_index > b_index;
197     }
198
199   if (c->descending)
200     retval = -retval;
201
202   return retval;
203 }
204
205 /* Performs SORT VARIABLES command. */
206 int
207 cmd_sort_variables (struct lexer *lexer, struct dataset *ds)
208 {
209   enum cmd_result result = CMD_FAILURE;
210
211   lex_match (lexer, T_BY);
212
213   /* Parse sort key. */
214   struct criterion c = { .attr_name = NULL };
215   if (lex_match_id (lexer, "NAME"))
216     c.key = K_NAME;
217   else if (lex_match_id (lexer, "TYPE"))
218     c.key = K_TYPE;
219   else if (lex_match_id (lexer, "FORMAT"))
220     c.key = K_FORMAT;
221   else if (lex_match_id (lexer, "LABEL"))
222     c.key = K_VAR_LABEL;
223   else if (lex_match_id (lexer, "VALUES"))
224     c.key = K_VALUE_LABELS;
225   else if (lex_match_id (lexer, "MISSING"))
226     c.key = K_MISSING_VALUES;
227   else if (lex_match_id (lexer, "MEASURE"))
228     c.key = K_MEASURE;
229   else if (lex_match_id (lexer, "ROLE"))
230     c.key = K_ROLE;
231   else if (lex_match_id (lexer, "COLUMNS"))
232     c.key = K_COLUMNS;
233   else if (lex_match_id (lexer, "ALIGNMENT"))
234     c.key = K_ALIGNMENT;
235   else if (lex_match_id (lexer, "ATTRIBUTE"))
236     {
237       if (!lex_force_id (lexer))
238         goto exit;
239       c.key = K_ATTRIBUTE;
240       c.attr_name = xstrdup (lex_tokcstr (lexer));
241       lex_get (lexer);
242     }
243
244   /* Parse sort direction. */
245   if (lex_match (lexer, T_LPAREN))
246     {
247       if (lex_match_id (lexer, "A") || lex_match_id (lexer, "UP"))
248         c.descending = false;
249       else if (lex_match_id (lexer, "D") || lex_match_id (lexer, "DOWN"))
250         c.descending = true;
251       else
252         {
253           lex_error (lexer, NULL);
254           goto exit;
255         }
256       if (!lex_force_match (lexer, T_RPAREN))
257         goto exit;
258     }
259   else
260     c.descending = false;
261
262   /* Sort variables. */
263   struct dictionary *d = dataset_dict (ds);
264   struct variable **vars;
265   size_t n_vars;
266   dict_get_vars_mutable (d, &vars, &n_vars, 0);
267   sort (vars, n_vars, sizeof *vars, compare_vars, &c);
268   dict_reorder_vars (d, CONST_CAST (struct variable *const *, vars), n_vars);
269   free (vars);
270
271   result = CMD_SUCCESS;
272
273 exit:
274   free (c.attr_name);
275   return result;
276 }