github.com/lmorg/murex@v0.0.0-20240217211045-e081c89cd4ef/builtins/core/datatools/count.go (about) 1 package datatools 2 3 import ( 4 "fmt" 5 "strconv" 6 "strings" 7 8 "github.com/lmorg/murex/config/defaults" 9 "github.com/lmorg/murex/lang" 10 "github.com/lmorg/murex/lang/parameters" 11 "github.com/lmorg/murex/lang/types" 12 "github.com/lmorg/murex/utils/json" 13 "github.com/lmorg/murex/utils/lists" 14 ) 15 16 func init() { 17 lang.DefineMethod("count", cmdCount, types.Unmarshal, types.Json) 18 19 defaults.AppendProfile(` 20 alias len=count --total 21 22 autocomplete: set count %[{ 23 FlagsDesc: { 24 "--duplications": "Output a JSON map of items and the number of their occurrences in a list or array" 25 "--unique": "Print the number of unique elements in a list or array" 26 "--sum": "Read an array, list or map from STDIN and output the sum of all the values (ignore non-numeric values)" 27 "--sum-strict": "Read an array, list or map from STDIN and output the sum of all the values (error on non-numeric values)" 28 "--total": "Read an array, list or map from STDIN and output the length for that array (default behaviour)" 29 } 30 }] 31 `) 32 } 33 34 const ( 35 argDuplications = "--duplications" 36 argUnique = "--unique" 37 argSum = "--sum" 38 argSumStrict = "--sum-strict" 39 argTotal = "--total" 40 ) 41 42 var argsCount = parameters.Arguments{ 43 AllowAdditional: false, 44 Flags: map[string]string{ 45 argDuplications: types.Boolean, 46 "-d": argDuplications, 47 argUnique: types.Boolean, 48 "-u": argUnique, 49 argSum: types.Boolean, 50 "-s": argSum, 51 argSumStrict: types.Boolean, 52 argTotal: types.Boolean, 53 "-t": argTotal, 54 }, 55 } 56 57 func cmdCount(p *lang.Process) error { 58 //p.Stdout.SetDataType(types.Json) 59 60 err := p.ErrIfNotAMethod() 61 if err != nil { 62 return err 63 } 64 65 flags, _, err := p.Parameters.ParseFlags(&argsCount) 66 if err != nil { 67 return err 68 } 69 70 if len(flags) == 0 { 71 flags[argTotal] = types.TrueString 72 } 73 74 for f := range flags { 75 switch f { 76 case argDuplications: 77 v, err := countDuplications(p) 78 if err != nil { 79 p.Stdout.SetDataType(types.Null) 80 return err 81 } 82 83 b, err := json.Marshal(v, p.Stdout.IsTTY()) 84 if err != nil { 85 p.Stdout.SetDataType(types.Null) 86 return err 87 } 88 89 p.Stdout.SetDataType(types.Json) 90 _, err = p.Stdout.Write(b) 91 return err 92 93 case argUnique: 94 v, err := countUnique(p) 95 if err != nil { 96 p.Stdout.SetDataType(types.Null) 97 return err 98 } 99 100 p.Stdout.SetDataType(types.Integer) 101 _, err = p.Stdout.Write([]byte(strconv.Itoa(v))) 102 return err 103 104 case argTotal: 105 v, err := countTotal(p) 106 if err != nil { 107 p.Stdout.SetDataType(types.Null) 108 return err 109 } 110 111 p.Stdout.SetDataType(types.Integer) 112 _, err = p.Stdout.Write([]byte(strconv.Itoa(v))) 113 return err 114 115 case argSum, argSumStrict: 116 f, err := countSum(p, flags[argSumStrict] == types.TrueString) 117 if err != nil { 118 p.Stdout.SetDataType(types.Null) 119 return err 120 } 121 122 p.Stdout.SetDataType(types.Number) 123 _, err = p.Stdout.Write([]byte(types.FloatToString(f))) 124 return err 125 } 126 } 127 128 return nil 129 } 130 131 func countDuplications(p *lang.Process) (map[string]int, error) { 132 v, err := lang.UnmarshalData(p, p.Stdin.GetDataType()) 133 if err != nil { 134 return make(map[string]int), err 135 } 136 137 return lists.Count(v) 138 } 139 140 func countUnique(p *lang.Process) (int, error) { 141 m, err := countDuplications(p) 142 if err != nil { 143 return 0, err 144 } 145 146 return len(m), nil 147 } 148 149 func countTotal(p *lang.Process) (int, error) { 150 v, err := lang.UnmarshalData(p, p.Stdin.GetDataType()) 151 if err != nil { 152 return 0, err 153 } 154 155 switch v := v.(type) { 156 case nil: 157 return 0, nil 158 159 case int, float64, string, bool: 160 return 1, nil 161 162 case []int: 163 return len(v), nil 164 case []float64: 165 return len(v), nil 166 case []string: 167 return len(v), nil 168 case []bool: 169 return len(v), nil 170 case []interface{}: 171 return len(v), nil 172 173 case map[string]string: 174 return len(v), nil 175 case map[interface{}]string: 176 return len(v), nil 177 178 case map[string]int: 179 return len(v), nil 180 case map[interface{}]int: 181 return len(v), nil 182 183 case map[string]float64: 184 return len(v), nil 185 case map[interface{}]float64: 186 return len(v), nil 187 188 case map[string]bool: 189 return len(v), nil 190 case map[interface{}]bool: 191 return len(v), nil 192 193 case map[string]interface{}: 194 return len(v), nil 195 case map[interface{}]interface{}: 196 return len(v), nil 197 198 case [][]string: 199 return len(v), nil 200 201 default: 202 return 0, fmt.Errorf("data type (%T) not supported, please report this at https://github.com/lmorg/murex/issues", v) 203 } 204 } 205 206 func countSum(p *lang.Process, strict bool) (float64, error) { 207 v, err := lang.UnmarshalData(p, p.Stdin.GetDataType()) 208 if err != nil { 209 return 0, err 210 } 211 212 switch t := v.(type) { 213 case nil: 214 return 0, nil 215 216 case int: 217 return float64(t), nil 218 case float64: 219 return t, nil 220 221 case []int: 222 return sumArrayNum(t) 223 case []float64: 224 return sumArrayNum(t) 225 226 case []string: 227 return sumArrayStr(t, strict) 228 case []interface{}: 229 return sumArrayStr(t, strict) 230 231 case map[string]int: 232 return sumMapNum(t) 233 case map[string]float64: 234 return sumMapNum(t) 235 case map[int]int: 236 return sumMapNum(t) 237 case map[int]float64: 238 return sumMapNum(t) 239 case map[float64]int: 240 return sumMapNum(t) 241 case map[float64]float64: 242 return sumMapNum(t) 243 case map[any]int: 244 return sumMapNum(t) 245 case map[any]float64: 246 return sumMapNum(t) 247 248 case map[string]string: 249 return sumMapStr(t, strict) 250 case map[string]any: 251 return sumMapStr(t, strict) 252 case map[int]string: 253 return sumMapStr(t, strict) 254 case map[int]any: 255 return sumMapStr(t, strict) 256 case map[float64]string: 257 return sumMapStr(t, strict) 258 case map[float64]any: 259 return sumMapStr(t, strict) 260 case map[any]string: 261 return sumMapStr(t, strict) 262 case map[any]any: 263 return sumMapStr(t, strict) 264 265 case [][]string: 266 slice := make([]string, len(t)) 267 for i := range t { 268 slice[i] = strings.Join(t[i], " ") 269 } 270 return sumArrayStr(slice, strict) 271 272 default: 273 return 0, fmt.Errorf("data type (%T) not supported, please report this at https://github.com/lmorg/murex/issues", v) 274 } 275 } 276 277 func sumArrayNum[N int | float64](slice []N) (float64, error) { 278 var n N 279 280 for _, v := range slice { 281 n += v 282 } 283 284 return float64(n), nil 285 } 286 287 func sumArrayStr[V any](slice []V, strict bool) (float64, error) { 288 var f float64 289 290 for i := range slice { 291 v, err := types.ConvertGoType(slice[i], types.Number) 292 if err != nil { 293 if strict { 294 return 0, fmt.Errorf(`cannot convert index %d to %s ("%v"): %s`, 295 i, types.Number, slice[i], err.Error()) 296 } else { 297 continue 298 } 299 } 300 f += v.(float64) 301 } 302 303 return f, nil 304 } 305 306 func sumMapNum[K comparable, V int | float64](m map[K]V) (float64, error) { 307 var n V 308 309 for _, v := range m { 310 n += v 311 } 312 313 return float64(n), nil 314 } 315 316 func sumMapStr[K comparable, V string | any](m map[K]V, strict bool) (float64, error) { 317 var f float64 318 319 for k, v := range m { 320 v, err := types.ConvertGoType(v, types.Number) 321 if err != nil { 322 if strict { 323 return 0, fmt.Errorf(`cannot convert index %v to %s ("%v"): %s`, 324 k, types.Number, v, err.Error()) 325 } else { 326 continue 327 } 328 } 329 f += v.(float64) 330 } 331 332 return f, nil 333 }