cat / 012
clone

a spreadsheet for the terminal: lotus 1-2-3 looks, google sheets keys and formulas, one go binary

cat/module-path
012 / internal / functions / lambda.go
lambda.go 5.6K
1package functions23import (4 "src.rest/cat/012/internal/formula"5 "src.rest/cat/012/internal/value"6)78// LET names values within a formula, and LAMBDA makes a function of9// names, called with arguments right away (LAMBDA(x, x*2)(3)), through a10// name LET gave it, or by MAP, REDUCE, BYROW, BYCOL, SCAN and MAKEARRAY11// for each entry of an array. The parser turns the names into Local12// nodes (formula), so named ranges never replace them.1314func init() {15 define(16 &FuncDef{Name: "LET", Args: "name1, value_expression1, [name2, value_expression2, ...], formula_expression", Desc: "Name values for use in a formula; an error counts only where its name is used", Min: 3, Max: -1, step: 2,17 eval: let, arrays: takesArrays, binds: formula.BindLet, format: func(args []Node, infer func(Node) Format) Format { return infer(args[len(args)-1]) }},18 &FuncDef{Name: "LAMBDA", Args: "[name, ...], formula_expression", Desc: "A function of names, called with values: LAMBDA(x, x*2)(3)", Min: 1, Max: -1,19 eval: makeLambda, arrays: takesArrays, binds: formula.BindLambda},20 &FuncDef{Name: "MAP", Args: "array1, [array2, ...], LAMBDA", Desc: "Each entry of arrays passed to a LAMBDA", Min: 2, Max: -1,21 eval: mapArrays, arrays: takesArrays},22 &FuncDef{Name: "REDUCE", Args: "initial_value, array_or_range, LAMBDA", Desc: "An array folded into one value by a LAMBDA of the total so far and each entry", Min: 3, Max: 3,23 eval: func(args []Node, get lookup) Value { return fold(args, get, false) }, arrays: takesArrays},24 &FuncDef{Name: "SCAN", Args: "initial_value, array_or_range, LAMBDA", Desc: "The running totals of REDUCE, one for each entry", Min: 3, Max: 3,25 eval: func(args []Node, get lookup) Value { return fold(args, get, true) }, arrays: takesArrays},26 &FuncDef{Name: "BYROW", Args: "array_or_range, LAMBDA", Desc: "Each row of an array passed to a LAMBDA, one value per row", Min: 2, Max: 2,27 eval: func(args []Node, get lookup) Value { return byLine(args, get, true) }, arrays: takesArrays},28 &FuncDef{Name: "BYCOL", Args: "array_or_range, LAMBDA", Desc: "Each column of an array passed to a LAMBDA, one value per column", Min: 2, Max: 2,29 eval: func(args []Node, get lookup) Value { return byLine(args, get, false) }, arrays: takesArrays},30 &FuncDef{Name: "MAKEARRAY", Args: "rows, columns, LAMBDA", Desc: "An array of a size, each entry a LAMBDA of its row and column", Min: 3, Max: 3,31 eval: makeArray, arrays: takesArrays},32 )33}3435func let(args []Node, get lookup) Value {36 outer := len(get.scope)37 defer func() { get.scope = get.scope[:outer] }()38 last := len(args) - 139 for i := 0; i < last; i += 2 {40 // An error is the result only where a name bound to it is used:41 // LET(x, 1/0, 5) is 5, as in Sheets.42 get.scope = append(get.scope, get.bind(args[i].(formula.Local).Name, args[i+1]))43 }44 return eval(args[last], get)45}4647func makeLambda(args []Node, get lookup) Value {48 l := &lambda{body: args[len(args)-1], scope: append([]binding(nil), get.scope...)}49 for _, a := range args[:len(args)-1] {50 l.params = append(l.params, a.(formula.Local).Name)51 }52 return get.handle(l)53}5455func mapArrays(args []Node, get lookup) Value {56 last := len(args) - 157 l, err := lambdaArg(args[last], get, last)58 if err != nil {59 return *err60 }61 arrs := make([]*Array, last)62 var s shape63 for i, a := range args[:last] {64 if arrs[i], err = arrayArg(a, get); err != nil {65 return *err66 }67 s.fit(arrs[i])68 }69 s.drows, s.dcols = s.rows, s.cols70 out, ok := s.newArray()71 if !ok {72 return value.ErrValue73 }74 vals := make([]Value, last)75 for r := range s.rows {76 for c := range s.cols {77 for i, a := range arrs {78 vals[i] = a.bcast(r, c)79 }80 out.Set(r, c, get.single(get.call(l, vals...)))81 }82 }83 return get.arrayValue(out)84}8586// fold is REDUCE, or SCAN with running: the LAMBDA called with the total87// so far and each entry, row by row.88func fold(args []Node, get lookup, running bool) Value {89 acc := eval1(args[0], get)90 a, err := arrayArg(args[1], get)91 if err != nil {92 return *err93 }94 l, err := lambdaArg(args[2], get, 2)95 if err != nil {96 return *err97 }98 if tooBig(a.Rows, a.Cols) {99 return value.ErrValue100 }101 var out *Array102 if running {103 out = NewArray(a.Rows, a.Cols)104 }105 for r := range a.Rows {106 for c := range a.Cols {107 acc = get.call(l, acc, a.At(r, c))108 if running {109 acc = get.single(acc)110 out.Set(r, c, acc)111 }112 }113 }114 if running {115 return get.arrayValue(out)116 }117 return acc118}119120// byLine is BYROW (rows) or BYCOL: the LAMBDA called with each line of121// the array, one value for each.122func byLine(args []Node, get lookup, rows bool) Value {123 a, err := arrayArg(args[0], get)124 if err != nil {125 return *err126 }127 l, err := lambdaArg(args[1], get, 1)128 if err != nil {129 return *err130 }131 n, width := a.Rows, a.Cols132 if !rows {133 n, width = a.Cols, a.Rows134 }135 if tooBig(n, width) {136 return value.ErrValue137 }138 out := NewArray(n, 1)139 if !rows {140 out = NewArray(1, n)141 }142 for i := range n {143 line := NewArray(1, width)144 if !rows {145 line = NewArray(width, 1)146 }147 for j := range width {148 if rows {149 line.Set(0, j, a.At(i, j))150 } else {151 line.Set(j, 0, a.At(j, i))152 }153 }154 out.V[i] = get.single(get.call(l, get.arrayValue(line)))155 }156 return get.arrayValue(out)157}158159func makeArray(args []Node, get lookup) Value {160 rows, err := intArg(args, 0, 0, get)161 if err != nil {162 return *err163 }164 cols, err := intArg(args, 1, 0, get)165 if err != nil {166 return *err167 }168 l, err := lambdaArg(args[2], get, 2)169 if err != nil {170 return *err171 }172 if rows < 1 || cols < 1 || tooBig(rows, cols) {173 return value.ErrValue174 }175 out := NewArray(rows, cols)176 for r := range rows {177 for c := range cols {178 out.Set(r, c, get.single(get.call(l, num(float64(r+1)), num(float64(c+1)))))179 }180 }181 return get.arrayValue(out)182}