-
Notifications
You must be signed in to change notification settings - Fork 4
/
histogram_map.go
275 lines (249 loc) · 7.17 KB
/
histogram_map.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
package histogram
import (
"errors"
"strconv"
"strings"
"github.com/grokify/gocharts/v2/data/table"
"github.com/grokify/mogo/errors/errorsutil"
"github.com/grokify/mogo/type/maputil"
"github.com/grokify/mogo/type/slicesutil"
"github.com/grokify/mogo/type/stringsutil"
"golang.org/x/exp/slices"
)
// AddMap provides a helper function to automatically create url encoded string keys.
// This can be used with `TableMap` to generate tables with arbitrary columns easily.
func (hist *Histogram) AddMap(binMap map[string]string, binCount int) {
m := maputil.MapStringString(binMap)
key := m.Encode()
hist.Add(key, binCount)
}
// MapKeySplit returns a new `HistogramSet` where the supplied key is the HistogramSet key.
func (hist *Histogram) MapKeySplit(mapKey string, mapValIncl []string) (*HistogramSet, error) {
hs := NewHistogramSet(mapKey)
mapValInclMap := map[string]int{}
for _, k := range mapValIncl {
mapValInclMap[k]++
}
/*
if 1 == 0 {
mk, err := hist.MapKeys()
logutil.FatalErr(err)
fmtutil.PrintJSON(mk)
mv, err := hist.MapKeyValues(mapKey, true)
logutil.FatalErr(err)
fmtutil.PrintJSON(mv)
panic("Z")
}
*/
for mapKeysStr, count := range hist.Bins {
binMap, err := maputil.ParseMapStringString(mapKeysStr)
if err != nil {
return nil, err
}
newBinMap := map[string]string{}
histName := ""
for k, v := range binMap {
if k == mapKey {
histName = v
} else {
newBinMap[k] = v
}
}
if len(mapValInclMap) > 0 {
if _, ok := mapValInclMap[histName]; !ok {
continue
}
}
subHist, ok := hs.HistogramMap[histName]
if !ok {
subHist = NewHistogram(histName)
}
subHist.AddMap(newBinMap, count)
hs.HistogramMap[histName] = subHist
}
return hs, nil
}
// MapKeysReduce returns a new `Histogram` with only the supplied keys present.
func (hist *Histogram) MapKeysReduce(mapKeysFilter []string) (*Histogram, error) {
mapKeysFilter = stringsutil.SliceCondenseSpace(mapKeysFilter, true, true)
filtered := NewHistogram(hist.Name)
if len(mapKeysFilter) == 0 || len(hist.Bins) == 0 {
return filtered, nil
}
for mapKeysStr, count := range hist.Bins {
binMap, err := maputil.ParseMapStringString(mapKeysStr)
if err != nil {
return nil, err
}
newBinMap := map[string]string{}
for _, filterKey := range mapKeysFilter {
if filterVal, ok := binMap[filterKey]; ok {
newBinMap[filterKey] = filterVal
} else {
newBinMap[filterKey] = ""
}
}
filtered.AddMap(newBinMap, count)
}
return filtered, nil
}
func (hist *Histogram) MapKeysFlattenSingle(mapKeyFilter string) (*Histogram, error) {
filtered := NewHistogram(hist.Name)
for mapKeyStr, count := range hist.Bins {
binMap, err := maputil.ParseMapStringString(mapKeyStr)
if err != nil {
return nil, err
}
if val, ok := binMap[mapKeyFilter]; ok {
filtered.Add(val, count)
} else {
filtered.Add("", count)
}
}
return filtered, nil
}
// MapKeys returns a list of keys using query string keys.
func (hist *Histogram) MapKeys() ([]string, error) {
keys := map[string]int{}
for qry := range hist.Bins {
m, err := maputil.ParseMapStringString(qry)
if err != nil {
return []string{}, err
}
for k := range m {
keys[k]++
}
}
return maputil.Keys(keys), nil
}
// MapKeyValues returns a list of keys using query string keys.
func (hist *Histogram) MapKeyValues(key string, dedupe bool) ([]string, error) {
vals := []string{}
for qry := range hist.Bins {
m, err := maputil.ParseMapStringString(qry)
if err != nil {
return []string{}, err
}
if v, ok := m[key]; ok {
vals = append(vals, v)
}
}
if dedupe {
vals = slicesutil.Dedupe(vals)
}
return vals, nil
}
func (hist *Histogram) TableSetMap(cfgs []HistogramMapTableConfig) (*table.TableSet, error) {
if len(cfgs) == 0 {
return nil, errors.New("error in `Histogram.TableSetMap`: table configs cannot be empty")
}
ts := table.NewTableSet(hist.Name)
for i, cfg := range cfgs {
if strings.TrimSpace(cfg.SplitKey) == "" {
tblName := cfg.TableName
if strings.TrimSpace(tblName) == "" {
tblName = "Sheet" + strconv.Itoa(i)
}
tbl, err := hist.TableMap(cfg.ColumnKeys, cfg.ColNameCount)
if err != nil {
return nil, errorsutil.Wrapf(err, "build TableSetMap for (%s)", tblName)
}
if slices.Index(ts.Order, tblName) > -1 {
return nil, errors.New("table name collision")
}
tbl.Name = tblName
ts.Order = append(ts.Order, tblName)
ts.TableMap[tbl.Name] = tbl
} else {
cfg.Inflate()
hset, err := hist.MapKeySplit(cfg.SplitKey, cfg.SplitValFilterIncl)
if err != nil {
return nil, err
}
hsetKeys := hset.ItemNames()
/*
fmtutil.PrintJSON(hsetKeys)
fmt.Printf("SPLIT KEY (%s) LEN(%d)\n", cfg.SplitKey, len(hset.HistogramMap))
panic("HERE")
*/
for _, hsetKey := range hsetKeys {
keyHist, ok := hset.HistogramMap[hsetKey]
if !ok {
panic("key not found")
}
tbl, err := keyHist.TableMap(cfg.ColumnKeys, cfg.ColNameCount)
if err != nil {
return nil, err
}
tblName := cfg.TableNamePrefix + hsetKey
ts.Order = append(ts.Order, tblName)
tbl.Name = tblName
ts.TableMap[tblName] = tbl
}
}
}
return ts, nil
}
type HistogramMapTableSetConfig struct {
Configs []HistogramMapTableConfig
}
type HistogramMapTableConfig struct {
TableName string
TableNamePrefix string
SplitKey string
SplitValFilterIncl []string // if present, only include these split values
ColumnKeys []string // doesn't include count column
ColNameCount string
splitValFilterMap map[string]int
// ColumnNames []string
}
func (cfg *HistogramMapTableConfig) Inflate() {
cfg.SplitValFilterIncl = stringsutil.SliceCondenseSpace(cfg.SplitValFilterIncl, true, true)
cfg.splitValFilterMap = map[string]int{}
for _, k := range cfg.SplitValFilterIncl {
cfg.splitValFilterMap[k]++
}
}
func (cfg *HistogramMapTableConfig) SplitValFilterInclExists(v string) bool {
if len(cfg.SplitValFilterIncl) != len(cfg.splitValFilterMap) {
cfg.Inflate()
}
if _, ok := cfg.splitValFilterMap[v]; ok {
return true
} else {
return false
}
}
// TableMap is used to generate a table using map keys.
func (hist *Histogram) TableMap(mapCols []string, colNameBinCount string) (*table.Table, error) {
if strings.TrimSpace(colNameBinCount) == "" {
colNameBinCount = "Count"
}
// create histogram with minimized aggregate map keys to aggregate exclude non-desired
// properties from the key for aggregation.
histSubset := NewHistogram("")
for binName, binCount := range hist.Bins {
binMap, err := maputil.ParseMapStringString(binName)
if err != nil {
return nil, err
}
newBinMap := binMap.Subset(mapCols, false, true, true)
// newBinMap := mapStringStringSubset(binMap, mapCols, true, false, true)
// fmtutil.PrintJSON(newBinMap)
histSubset.AddMap(newBinMap, binCount)
}
tbl := table.NewTable(hist.Name)
tbl.Columns = append(mapCols, colNameBinCount)
for binName, binCount := range histSubset.Bins {
binMap, err := maputil.ParseMapStringString(binName)
if err != nil {
return nil, err
}
binVals := binMap.Gets(true, mapCols)
tbl.Rows = append(tbl.Rows,
append(binVals, strconv.Itoa(binCount)),
)
}
tbl.FormatMap = map[int]string{len(tbl.Columns) - 1: "int"}
return &tbl, nil
}