mirror of
https://github.com/johnkerl/miller.git
synced 2026-07-27 09:48:46 +00:00
246 lines
8.9 KiB
C
246 lines
8.9 KiB
C
// ================================================================
|
|
// Array-only (open addressing) multi-level hash map, with linear probing for collisions.
|
|
// All keys, and terminal-level values, are mlrvals. All data passed into the put method
|
|
// are copied; no pointers in this data structure reference anything external.
|
|
//
|
|
// Notes:
|
|
// * null key is not supported.
|
|
// * null value is not supported.
|
|
//
|
|
// See also:
|
|
// * http://en.wikipedia.org/wiki/Hash_table
|
|
// * http://docs.oracle.com/javase/6/docs/api/java/util/Map.html
|
|
// ================================================================
|
|
|
|
#ifndef MLHMMV_H
|
|
#define MLHMMV_H
|
|
|
|
#include "containers/mlrval.h"
|
|
#include "containers/sllmv.h"
|
|
#include "containers/sllv.h"
|
|
#include "containers/lrec.h"
|
|
|
|
#define MLHMMV_ERROR_NONE 0x0000
|
|
#define MLHMMV_ERROR_KEYLIST_TOO_DEEP 0xdeef
|
|
#define MLHMMV_ERROR_KEYLIST_TOO_SHALLOW 0x58a1
|
|
|
|
// This is made visible here in the API so the unit-tester can be sure to exercise the resize logic.
|
|
#define MLHMMV_INITIAL_ARRAY_LENGTH 16
|
|
|
|
struct _mlhmmv_level_t; // forward reference
|
|
|
|
// ----------------------------------------------------------------
|
|
typedef struct _mlhmmv_value_t {
|
|
int is_terminal;
|
|
// xxx needs absent/null initters respectively in the .c file
|
|
mv_t mlrval;
|
|
struct _mlhmmv_level_t* pnext_level;
|
|
} mlhmmv_value_t;
|
|
|
|
typedef struct _mlhmmv_level_entry_t {
|
|
int ideal_index;
|
|
mv_t level_key;
|
|
mlhmmv_value_t level_value; // terminal mlrval, or another hashmap
|
|
struct _mlhmmv_level_entry_t *pprev;
|
|
struct _mlhmmv_level_entry_t *pnext;
|
|
} mlhmmv_level_entry_t;
|
|
|
|
typedef unsigned char mlhmmv_level_entry_state_t;
|
|
|
|
// Store a mlrval into the mlhmmv_value without copying, implicitly transferring
|
|
// ownership of the mlrval's free_flags. This means the mlrval will be freed
|
|
// when the mlhmmv_value is freed, so the caller should make a copy first if
|
|
// necessary.
|
|
//
|
|
// This is a hot path for non-map local-variable assignments.
|
|
static inline mlhmmv_value_t mlhmmv_value_transfer_terminal(mv_t val) { // xxx temp?
|
|
return (mlhmmv_value_t) {.is_terminal = TRUE, .mlrval = val, .pnext_level = NULL};
|
|
}
|
|
|
|
// ----------------------------------------------------------------
|
|
typedef struct _mlhmmv_level_t {
|
|
int num_occupied;
|
|
int num_freed;
|
|
int array_length;
|
|
mlhmmv_level_entry_t* entries;
|
|
mlhmmv_level_entry_state_t* states;
|
|
mlhmmv_level_entry_t* phead;
|
|
mlhmmv_level_entry_t* ptail;
|
|
} mlhmmv_level_t;
|
|
|
|
// xxx temp expose
|
|
mlhmmv_level_t* mlhmmv_level_alloc();
|
|
|
|
// ----------------------------------------------------------------
|
|
typedef struct _mlhmmv_t {
|
|
mlhmmv_level_t* proot_level;
|
|
} mlhmmv_t;
|
|
|
|
mlhmmv_t* mlhmmv_alloc();
|
|
mlhmmv_value_t mlhmmv_value_alloc_empty_map();
|
|
|
|
void mlhmmv_free(mlhmmv_t* pmap);
|
|
|
|
void mlhmmv_put_terminal(mlhmmv_t* pmap, sllmv_t* pmvkeys, mv_t* pterminal_value);
|
|
void mlhmmv_put_value_at_level_aux(mlhmmv_level_t* plevel, sllmve_t* prest_keys, mlhmmv_value_t* pvalue); // xxx rename
|
|
|
|
// If the return value is non-null, error will be MLHMMV_ERROR_NONE. If the
|
|
// return value is null, the error will be MLHMMV_ERROR_KEYLIST_TOO_DEEP or
|
|
// MLHMMV_ERROR_KEYLIST_TOO_SHALLOW, or MLHMMV_ERROR_NONE if the keylist matches
|
|
// map depth but the entry is not found.
|
|
//
|
|
// Note: this returns a pointer to the map's data, not to a copy.
|
|
// The caller shouldn't free it, or modify it.
|
|
mv_t* mlhmmv_get_terminal(mlhmmv_t* pmap, sllmv_t* pmvkeys, int* perror);
|
|
mv_t* mlhmmv_get_terminal_from_level(mlhmmv_level_t* plevel, sllmv_t* pmvkeys, int* perror);
|
|
|
|
// These are an optimization for assignment from full srec, e.g. '@records[$key1][$key2] = $*'.
|
|
// Using mlhmmv_get_or_create_level, the CST logic can get or create the @records[$key1][$key2]
|
|
// level of the mlhmmv, then copy values there.
|
|
mlhmmv_level_t* mlhmmv_get_or_create_level(mlhmmv_t* pmap, sllmv_t* pmvkeys);
|
|
void mlhmmv_put_terminal_from_level(mlhmmv_level_t* plevel, sllmve_t* prest_keys, mv_t* pterminal_value);
|
|
mlhmmv_level_t* mlhmmv_put_empty_map_from_level(mlhmmv_level_t* plevel, sllmve_t* prest_keys);
|
|
|
|
// This is an assignment for assignment to full srec, e.g. '$* = @records[$key1][$key2]'.
|
|
// The CST logic can use this function to get the @records[$key1][$key2] level of the mlhmmv,
|
|
// then copy values from there.
|
|
mlhmmv_level_t* mlhmmv_get_level(mlhmmv_t* pmap, sllmv_t* pmvkeys, int* perror);
|
|
// This is for getting submaps out of local variables.
|
|
mlhmmv_value_t* mlhmmv_get_value_from_level(mlhmmv_level_t* plevel, sllmv_t* pmvkeys, int* perror);
|
|
|
|
// For oosvar-to-oosvar assignment.
|
|
void mlhmmv_copy(mlhmmv_t* pmap, sllmv_t* ptokeys, sllmv_t* pfromkeys);
|
|
|
|
// For for-loop-over-oosvar, wherein we need to copy the submap before iterating over it
|
|
// (since the iteration may modify it). If the keys don't index a submap, then the return
|
|
// value has is_terminal = TRUE and pnext_level = NULL.
|
|
mlhmmv_value_t mlhmmv_copy_submap_from_root(mlhmmv_t* pmap, sllmv_t* pmvkeys);
|
|
mlhmmv_value_t mlhmmv_copy_aux(mlhmmv_value_t* pvalue); // xxx rename
|
|
void mlhmmv_free_submap(mlhmmv_value_t submap);
|
|
|
|
// xxx comment context
|
|
sllv_t* mlhmmv_copy_keys_from_submap(mlhmmv_t* pmap, sllmv_t* pmvkeys);
|
|
// xxx comment context
|
|
sllv_t* mlhmmv_copy_keys_from_submap_xxx_rename(mlhmmv_value_t* pmvalue, sllmv_t* pmvkeys);
|
|
|
|
// Unset value/submap from a specified level onward, also unsetting any maps which become empty as a result.
|
|
// Examples:
|
|
// {
|
|
// "a" : { "x" : 1, "y" : 2 },
|
|
// "b" : { "x" : 3, "y" : 4 },
|
|
// }
|
|
// with pmvkeys = ["a"] leaves
|
|
// {
|
|
// "b" : { "x" : 3, "y" : 4 },
|
|
// }
|
|
// but with pmvkeys = ["a", "y"] leaves
|
|
// {
|
|
// "a" : { "x" : 1 },
|
|
// "b" : { "x" : 3, "y" : 4 },
|
|
// }
|
|
// and with pmvkeys = [] leaves
|
|
// {
|
|
// }
|
|
// Now if ["a","x"] is removed from
|
|
// {
|
|
// "a" : { "x" : 1 },
|
|
// "b" : { "x" : 3, "y" : 4 },
|
|
// }
|
|
// then
|
|
// {
|
|
// "b" : { "x" : 3, "y" : 4 },
|
|
// }
|
|
// is left: unsetting "a":"x" leaves the map at "a" so this is unset as well.
|
|
void mlhmmv_remove(mlhmmv_t* pmap, sllmv_t* pmvkeys);
|
|
|
|
void mlhmmv_clear_level(mlhmmv_level_t* plevel);
|
|
|
|
// For 'emit' and 'emitp' in the DSL. These allocate lrecs, appended to the poutrecs list.
|
|
// * pmap is the base-level oosvar multi-level hashmap.
|
|
// * pkeys specify the level in the mlhmmv at which to produce data.
|
|
// * pnames is used to pull subsequent-level keys out into separate fields.
|
|
// * In case pnames isn't long enough to reach a terminal mlrval level in the mlhmmv,
|
|
// do_full_prefixing specifies whether to concatenate nested mlhmmv keys into single lrec keys.
|
|
//
|
|
// Examples:
|
|
|
|
// * pkeys reaches a terminal level:
|
|
//
|
|
// $ mlr --opprint put -q '@sum += $x; end { emit @sum }' ../data/small
|
|
// sum
|
|
// 4.536294
|
|
|
|
// * pkeys reaches terminal levels:
|
|
//
|
|
// $ mlr --opprint put -q '@sum[$a][$b] += $x; end { emit @sum, "a", "b" }' ../data/small
|
|
// a b sum
|
|
// pan pan 0.346790
|
|
// pan wye 0.502626
|
|
// eks pan 0.758680
|
|
// eks wye 0.381399
|
|
// eks zee 0.611784
|
|
// wye wye 0.204603
|
|
// wye pan 0.573289
|
|
// zee pan 0.527126
|
|
// zee wye 0.598554
|
|
// hat wye 0.031442
|
|
|
|
// * pkeys reaches non-terminal levels: non-prefixed:
|
|
//
|
|
// $ mlr --opprint put -q '@sum[$a][$b] += $x; end { emit @sum, "a" }' ../data/small
|
|
// a pan wye
|
|
// pan 0.346790 0.502626
|
|
//
|
|
// a pan wye zee
|
|
// eks 0.758680 0.381399 0.611784
|
|
//
|
|
// a wye pan
|
|
// wye 0.204603 0.573289
|
|
//
|
|
// a pan wye
|
|
// zee 0.527126 0.598554
|
|
//
|
|
// a wye
|
|
// hat 0.031442
|
|
|
|
// * pkeys reaches non-terminal levels: prefixed:
|
|
//
|
|
// $ mlr --opprint put -q '@sum[$a][$b] += $x; end { emitp @sum, "a" }' ../data/small
|
|
// a sum:pan sum:wye
|
|
// pan 0.346790 0.502626
|
|
//
|
|
// a sum:pan sum:wye sum:zee
|
|
// eks 0.758680 0.381399 0.611784
|
|
//
|
|
// a sum:wye sum:pan
|
|
// wye 0.204603 0.573289
|
|
//
|
|
// a sum:pan sum:wye
|
|
// zee 0.527126 0.598554
|
|
//
|
|
// a sum:wye
|
|
// hat 0.031442
|
|
|
|
void mlhmmv_to_lrecs(mlhmmv_t* pmap, sllmv_t* pkeys, sllmv_t* pnames, sllv_t* poutrecs,
|
|
int do_full_prefixing, char* flatten_separator);
|
|
|
|
void mlhmmv_to_lrecs_lashed(mlhmmv_value_t** ptop_values, int num_submaps, sllmv_t** ppkeys, sllmv_t* pnames,
|
|
sllv_t* poutrecs, int do_full_prefixing, char* flatten_separator);
|
|
|
|
// xxx rm?
|
|
void mlhmmv_level_to_lrecs(mlhmmv_level_t* plevel, sllmv_t* pkeys, sllmv_t* pnames, sllv_t* poutrecs,
|
|
int do_full_prefixing, char* flatten_separator);
|
|
|
|
// For 'emit all' and 'emitp all' in the DSL
|
|
void mlhmmv_all_to_lrecs(mlhmmv_t* pmap, sllmv_t* pnames, sllv_t* poutrecs,
|
|
int do_full_prefixing, char* flatten_separator);
|
|
|
|
// For 'dump' in the DSL; also used by the lrec-to-JSON writer.
|
|
void mlhmmv_print_json_stacked(mlhmmv_t* pmap, int quote_values_always, char* line_indent, FILE* ostream);
|
|
void mlhmmv_print_terminal(mv_t* pmv, int quote_values_always, FILE* ostream);
|
|
void mlhmmv_print_json_single_line(mlhmmv_t* pmap, int quote_values_always, FILE* ostream);
|
|
|
|
void mlhmmv_level_print_stacked(mlhmmv_level_t* plevel, int depth,
|
|
int do_final_comma, int quote_values_always, char* line_indent, FILE* ostream);
|
|
|
|
#endif // MLHMMV_H
|