+}
+
+static double
+ctables_summary_value (union ctables_summary *s,
+ const struct ctables_summary_spec *ss)
+{
+ switch (ss->function)
+ {
+ case CTSF_COUNT:
+ case CTSF_ECOUNT:
+ return s->valid;
+
+ case CTSF_ROWPCT_COUNT:
+ case CTSF_COLPCT_COUNT:
+ case CTSF_TABLEPCT_COUNT:
+ case CTSF_SUBTABLEPCT_COUNT:
+ case CTSF_LAYERPCT_COUNT:
+ case CTSF_LAYERROWPCT_COUNT:
+ case CTSF_LAYERCOLPCT_COUNT:
+ case CTSF_ROWPCT_VALIDN:
+ case CTSF_COLPCT_VALIDN:
+ case CTSF_TABLEPCT_VALIDN:
+ case CTSF_SUBTABLEPCT_VALIDN:
+ case CTSF_LAYERPCT_VALIDN:
+ case CTSF_LAYERROWPCT_VALIDN:
+ case CTSF_LAYERCOLPCT_VALIDN:
+ case CTSF_ROWPCT_TOTALN:
+ case CTSF_COLPCT_TOTALN:
+ case CTSF_TABLEPCT_TOTALN:
+ case CTSF_SUBTABLEPCT_TOTALN:
+ case CTSF_LAYERPCT_TOTALN:
+ case CTSF_LAYERROWPCT_TOTALN:
+ case CTSF_LAYERCOLPCT_TOTALN:
+ NOT_REACHED ();
+
+ case CSTF_TOTALN:
+ case CTSF_ETOTALN:
+ return s->valid + s->missing;
+
+ case CTSF_VALIDN:
+ case CTSF_EVALIDN:
+ return s->valid;
+
+ case CTSF_MAXIMUM:
+ return s->max;
+
+ case CTSF_MINIMUM:
+ return s->min;
+
+ case CTSF_RANGE:
+ return s->max != SYSMIS && s->min != SYSMIS ? s->max - s->min : SYSMIS;
+
+ case CTSF_MEAN:
+ {
+ double mean;
+ moments1_calculate (s->moments, NULL, &mean, NULL, NULL, NULL);
+ return mean;
+ }
+
+ case CTSF_SEMEAN:
+ {
+ double weight, variance;
+ moments1_calculate (s->moments, &weight, NULL, &variance, NULL, NULL);
+ return calc_semean (variance, weight);
+ }
+
+ case CTSF_STDDEV:
+ {
+ double variance;
+ moments1_calculate (s->moments, NULL, NULL, &variance, NULL, NULL);
+ return variance != SYSMIS ? sqrt (variance) : SYSMIS;
+ }
+
+ case CTSF_SUM:
+ {
+ double weight, mean;
+ moments1_calculate (s->moments, &weight, &mean, NULL, NULL, NULL);
+ return weight != SYSMIS && mean != SYSMIS ? weight * mean : SYSMIS;
+ }
+
+ case CTSF_VARIANCE:
+ {
+ double variance;
+ moments1_calculate (s->moments, NULL, NULL, &variance, NULL, NULL);
+ return variance;
+ }
+
+ case CTSF_ROWPCT_SUM:
+ case CTSF_COLPCT_SUM:
+ case CTSF_TABLEPCT_SUM:
+ case CTSF_SUBTABLEPCT_SUM:
+ case CTSF_LAYERPCT_SUM:
+ case CTSF_LAYERROWPCT_SUM:
+ case CTSF_LAYERCOLPCT_SUM:
+ NOT_REACHED ();
+
+ case CTSF_MEDIAN:
+ case CTSF_MISSING:
+ case CTSF_MODE:
+ case CTSF_PTILE:
+ NOT_REACHED ();
+
+ case CTSF_RESPONSES:
+ case CTSF_ROWPCT_RESPONSES:
+ case CTSF_COLPCT_RESPONSES:
+ case CTSF_TABLEPCT_RESPONSES:
+ case CTSF_SUBTABLEPCT_RESPONSES:
+ case CTSF_LAYERPCT_RESPONSES:
+ case CTSF_LAYERROWPCT_RESPONSES:
+ case CTSF_LAYERCOLPCT_RESPONSES:
+ case CTSF_ROWPCT_RESPONSES_COUNT:
+ case CTSF_COLPCT_RESPONSES_COUNT:
+ case CTSF_TABLEPCT_RESPONSES_COUNT:
+ case CTSF_SUBTABLEPCT_RESPONSES_COUNT:
+ case CTSF_LAYERPCT_RESPONSES_COUNT:
+ case CTSF_LAYERROWPCT_RESPONSES_COUNT:
+ case CTSF_LAYERCOLPCT_RESPONSES_COUNT:
+ case CTSF_ROWPCT_COUNT_RESPONSES:
+ case CTSF_COLPCT_COUNT_RESPONSES:
+ case CTSF_TABLEPCT_COUNT_RESPONSES:
+ case CTSF_SUBTABLEPCT_COUNT_RESPONSES:
+ case CTSF_LAYERPCT_COUNT_RESPONSES:
+ case CTSF_LAYERROWPCT_COUNT_RESPONSES:
+ case CTSF_LAYERCOLPCT_COUNT_RESPONSES:
+ NOT_REACHED ();
+ }
+
+ NOT_REACHED ();
+}
+
+struct ctables_freq
+ {
+ struct hmap_node node; /* Element in hash table. */
+
+ struct
+ {
+ size_t vaa_idx;
+ union value *values;
+ int leaf;
+ }
+ axes[PIVOT_N_AXES];
+
+ union ctables_summary *summaries;
+ };
+
+#if 0
+static struct ctables_freq *
+ctables_freq_create (struct ctables_freqtab *ft)
+{
+ struct ctables_freq *f = xmalloc (sizeof *f + ft->vars.n * sizeof *f->values);
+ f->summaries = xmalloc (ft->n_summaries * sizeof *f->summaries);
+ for (size_t i = 0; i < ft->n_summaries; i++)
+ ctables_summary_init (&f->summaries[i], &ft->summaries[i]);
+ return f;
+}
+
+static void
+ctables_freq_add (struct ctables_freqtab *ft, struct ctables_freq *f,
+ const struct variable *var, const union value *value,
+ double weight)
+{
+ for (size_t i = 0; i < ft->n_summaries; i++)
+ ctables_summary_add (&f->summaries[i], &ft->summaries[i],
+ var, value, weight);
+}
+#endif
+
+struct ctables_freq_sort_aux
+ {
+ const struct ctables_table *t;
+ enum pivot_axis_type a;
+ };
+
+static int
+ctables_freq_compare_3way (const void *a_, const void *b_, const void *aux_)
+{
+ const struct ctables_freq_sort_aux *aux = aux_;
+ struct ctables_freq *const *ap = a_;
+ struct ctables_freq *const *bp = b_;
+ const struct ctables_freq *a = *ap;
+ const struct ctables_freq *b = *bp;
+
+ size_t a_idx = a->axes[aux->a].vaa_idx;
+ size_t b_idx = b->axes[aux->a].vaa_idx;
+ if (a_idx != b_idx)
+ return a_idx < b_idx ? -1 : 1;
+
+ const struct var_array *va = &aux->t->vaas[aux->a].vas[a_idx];
+ for (size_t i = 0; i < va->n; i++)
+ if (i != va->scale_idx)
+ {
+ int cmp = value_compare_3way (&a->axes[aux->a].values[i],
+ &b->axes[aux->a].values[i],
+ var_get_width (va->vars[i]));
+ if (cmp)
+ return cmp;
+ }