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  }