+/* Transformation for adding measurement level. */
+
+struct measurement_level_value
+ {
+ struct hmap_node hmap_node;
+ double value;
+ };
+
+struct measurement_level_var
+ {
+ struct variable *var;
+ struct hmap *values;
+ };
+
+static void
+add_measurement_level_var_uninit (struct measurement_level_var *mlv)
+{
+ struct measurement_level_value *mlvalue, *next;
+ HMAP_FOR_EACH_SAFE (mlvalue, next, struct measurement_level_value, hmap_node,
+ mlv->values)
+ {
+ hmap_delete (mlv->values, &mlvalue->hmap_node);
+ free (mlvalue);
+ }
+ hmap_destroy (mlv->values);
+ free (mlv->values);
+}
+
+static enum measure
+add_measurement_level_var_interpret (const struct measurement_level_var *mlv)
+{
+ size_t n = hmap_count (mlv->values);
+ if (!n)
+ {
+ /* All missing (or no data). */
+ return MEASURE_NOMINAL;
+ }
+
+ const struct measurement_level_value *mlvalue;
+ HMAP_FOR_EACH (mlvalue, struct measurement_level_value, hmap_node,
+ mlv->values)
+ if (mlvalue->value < 10)
+ return MEASURE_NOMINAL;
+ return MEASURE_SCALE;
+}
+
+struct measurement_level_trns
+ {
+ struct measurement_level_var *vars;
+ size_t n_vars;
+ };
+
+static enum measure
+add_measurement_level_trns_proc__ (struct measurement_level_var *mlv, double value)
+{
+ if (var_is_num_missing (mlv->var, value))
+ return MEASURE_UNKNOWN;
+ else if (value < 0 || value != floor (value))
+ return MEASURE_SCALE;
+
+ size_t hash = hash_double (value, 0);
+ struct measurement_level_value *mlvalue;
+ HMAP_FOR_EACH_WITH_HASH (mlvalue, struct measurement_level_value, hmap_node,
+ hash, mlv->values)
+ if (mlvalue->value == value)
+ return MEASURE_UNKNOWN;
+
+ mlvalue = xmalloc (sizeof *mlvalue);
+ mlvalue->value = value;
+ hmap_insert (mlv->values, &mlvalue->hmap_node, hash);
+ if (hmap_count (mlv->values) >= settings_get_scalemin ())
+ return MEASURE_SCALE;
+
+ return MEASURE_UNKNOWN;
+}
+
+static enum trns_result
+add_measurement_level_trns_proc (void *mlt_, struct ccase **c,
+ casenumber case_nr UNUSED)
+{
+ struct measurement_level_trns *mlt = mlt_;
+ for (size_t i = 0; i < mlt->n_vars; )
+ {
+ struct measurement_level_var *mlv = &mlt->vars[i];
+ double value = case_num (*c, mlv->var);
+ enum measure m = add_measurement_level_trns_proc__ (mlv, value);
+ if (m != MEASURE_UNKNOWN)
+ {
+ var_set_measure (mlv->var, m);
+
+ add_measurement_level_var_uninit (mlv);
+ *mlv = mlt->vars[--mlt->n_vars];
+ }
+ else
+ i++;
+ }
+ return TRNS_CONTINUE;
+}
+
+static void
+add_measurement_level_trns_free__ (struct measurement_level_trns *mlt)
+{
+ for (size_t i = 0; i < mlt->n_vars; i++)
+ {
+ struct measurement_level_var *mlv = &mlt->vars[i];
+ var_set_measure (mlv->var, add_measurement_level_var_interpret (mlv));
+ add_measurement_level_var_uninit (mlv);
+ }
+ free (mlt->vars);
+ free (mlt);
+}
+
+static bool
+add_measurement_level_trns_free (void *mlt_)
+{
+ struct measurement_level_trns *mlt = mlt_;
+ for (size_t i = 0; i < mlt->n_vars; i++)
+ {
+ struct measurement_level_var *mlv = &mlt->vars[i];
+ var_set_measure (mlv->var, add_measurement_level_var_interpret (mlv));
+ }
+ add_measurement_level_trns_free__ (mlt);
+ return true;
+}
+
+static const struct trns_class add_measurement_level_trns_class = {
+ .name = "add measurement level",
+ .execute = add_measurement_level_trns_proc,
+ .destroy = add_measurement_level_trns_free,
+};
+
+static void
+add_measurement_level_trns (struct dataset *ds, struct dictionary *dict)
+{
+ struct variable **vars = NULL;
+ size_t n_vars = 0;
+ size_t allocated_vars = 0;
+
+ for (size_t i = 0; i < dict_get_n_vars (dict); i++)
+ {
+ struct variable *var = dict_get_var (dict, i);
+ if (var_get_measure (var) != MEASURE_UNKNOWN)
+ continue;
+
+ const struct fmt_spec *f = var_get_print_format (var);
+ enum measure m = var_default_measure_for_format (f->type);
+ if (m != MEASURE_UNKNOWN)
+ {
+ var_set_measure (var, m);
+ continue;
+ }
+
+ if (n_vars >= allocated_vars)
+ vars = x2nrealloc (vars, &allocated_vars, sizeof *vars);
+ vars[n_vars++] = var;
+ }
+
+ if (!n_vars)
+ return;
+
+ /* We do this as a second step because otherwise we'd be moving hmaps around,
+ which doesn't work. */
+ struct measurement_level_var *mlvs = xmalloc (n_vars * sizeof *mlvs);
+ for (size_t i = 0; i < n_vars; i++)
+ {
+ mlvs[i].var = vars[i];
+ mlvs[i].values = xmalloc (sizeof *mlvs[i].values);
+ hmap_init (mlvs[i].values);
+ }
+ free (vars);
+
+ struct measurement_level_trns *mlt = xmalloc (sizeof *mlt);
+ *mlt = (struct measurement_level_trns) {
+ .vars = mlvs,
+ .n_vars = n_vars,
+ };
+ add_transformation (ds, &add_measurement_level_trns_class, mlt);
+}
+
+static void
+cancel_measurement_level_trns (struct trns_chain *chain)
+{
+ if (!chain->n)
+ return;
+
+ struct transformation *trns = &chain->xforms[chain->n - 1];
+ if (trns->class != &add_measurement_level_trns_class)
+ return;
+
+ struct measurement_level_trns *mlt = trns->aux;
+ add_measurement_level_trns_free__ (mlt);
+ chain->n--;
+}
+\f