diff --git a/src/comp/funcs.go b/src/comp/funcs.go index efb0094..86fc139 100644 --- a/src/comp/funcs.go +++ b/src/comp/funcs.go @@ -8,24 +8,33 @@ import ( "strings" ) +type State interface{} + type Func struct { Name string Type FuncType - Eval func(s *Stack) + Eval func(state State, s *Stack) + + InitState func() State + State State +} + +func noop() State { + return nil } func FuncTrunc() *Func { t := FuncType{ScalarType(0), []Type{ScalarType(0)}} - return &Func{"trunc", t, func(s *Stack) { + return &Func{"trunc", t, func(state State, s *Stack) { val := s.PopNum() val = math.Trunc(val) s.PushNum(val) - }} + }, noop, nil} } func FuncDist() *Func { t := FuncType{ScalarType(0), []Type{ScalarType(0), ScalarType(0), ScalarType(0), ScalarType(0)}} - return &Func{"dist", t, func(s *Stack) { + return &Func{"dist", t, func(state State, s *Stack) { lat1 := s.PopNum() lon1 := s.PopNum() lat2 := s.PopNum() @@ -34,53 +43,57 @@ func FuncDist() *Func { val := Dist(lat1, lon1, lat2, lon2) s.PushNum(val) - }} + }, noop, nil} } func FuncTrim() *Func { t := FuncType{ScalarType(0), []Type{ScalarType(0)}} - return &Func{"trim", t, func(s *Stack) { + return &Func{"trim", t, func(state State, s *Stack) { str := s.PopStr() str = strings.Trim(str, " \t\r\n") s.PushStr(str) - }} + }, noop, nil} } func FuncLower() *Func { t := FuncType{ScalarType(0), []Type{ScalarType(0)}} - return &Func{"lower", t, func(s *Stack) { + return &Func{"lower", t, func(state State, s *Stack) { str := s.PopStr() str = strings.ToLower(str) s.PushStr(str) - }} + }, noop, nil} } func FuncUpper() *Func { t := FuncType{ScalarType(0), []Type{ScalarType(0)}} - return &Func{"upper", t, func(s *Stack) { + return &Func{"upper", t, func(state State, s *Stack) { str := s.PopStr() str = strings.ToUpper(str) s.PushStr(str) - }} + }, noop, nil} } func FuncFuzzy() *Func { t := FuncType{ScalarType(0), []Type{ScalarType(0), ScalarType(0)}} - return &Func{"fuzzy", t, func(s *Stack) { + return &Func{"fuzzy", t, func(state State, s *Stack) { + f := state.(*Fuzzy) + se := s.PopStr() te := s.PopStr() - val := Fuzzy(se, te) + val := f.Compare(se, te) s.PushNum(val) - }} + }, func() State { + return new(Fuzzy) + }, new(Fuzzy)} } func FuncReplace() *Func { t := FuncType{ScalarType(0), []Type{ScalarType(0), ScalarType(0), ScalarType(0)}} - return &Func{"replace", t, func(s *Stack) { + return &Func{"replace", t, func(state State, s *Stack) { str := s.PopStr() from := s.PopStr() to := s.PopStr() str = strings.Replace(str, from, to, -1) s.PushStr(str) - }} + }, noop, nil} } diff --git a/src/comp/fuzzy.go b/src/comp/fuzzy.go index e0b047a..caf96bd 100644 --- a/src/comp/fuzzy.go +++ b/src/comp/fuzzy.go @@ -3,16 +3,31 @@ package main -import ( - . "math" -) +type Fuzzy struct { + m, n int + d [][]int +} func min(a, b, c int) int { - m := Min(float64(a), float64(b)) - return int(Min(m, float64(c))) + min := c + if b < c { + min = b + } + if a < min { + min = a + } + return min +} + +func max(a, b int) int { + max := b + if a > b { + max = a + } + return max } -func dist(left, right string) int { +func (f *Fuzzy) dist(left, right string) int { s := []rune(left) t := []rune(right) @@ -25,44 +40,48 @@ func dist(left, right string) int { m := len(s) + 1 n := len(t) + 1 - var d [][]int - d = make([][]int, m) - for i := 0; i < m; i++ { - d[i] = make([]int, n) - } + if f.m < m || f.n < n { + f.m = m + f.n = n - for i := 0; i < m; i++ { - d[i][0] = i // the distance of any first string to an empty second string - } - for j := 0; j < n; j++ { - d[0][j] = j // the distance of any second string to an empty first string + f.d = make([][]int, m) + for i := 0; i < m; i++ { + f.d[i] = make([]int, n) + } + + for i := 0; i < m; i++ { + f.d[i][0] = i // the distance of first string to an empty second string + } + for j := 0; j < n; j++ { + f.d[0][j] = j // the distance of second string to an empty first string + } } for j := 1; j < n; j++ { for i := 1; i < m; i++ { if s[i-1] == t[j-1] { - d[i][j] = d[i-1][j-1] // no operation required + f.d[i][j] = f.d[i-1][j-1] // no operation required } else { - d[i][j] = min( - d[i-1][j]+1, // a deletion - d[i][j-1]+1, // an insertion - d[i-1][j-1]+1) // a substitution + f.d[i][j] = min( + f.d[i-1][j]+1, // a deletion + f.d[i][j-1]+1, // an insertion + f.d[i-1][j-1]+1) // a substitution } } } - return d[m-1][n-1] + return f.d[m-1][n-1] } -func Fuzzy(left, right string) float64 { - d := float64(dist(left, right)) +func (f *Fuzzy) Compare(left, right string) float64 { + d := float64(f.dist(left, right)) if d == 0 { return 1 } s := []rune(left) t := []rune(right) - l := Max(float64(len(s)), float64(len(t))) + l := float64(max(len(s), len(t))) return (l - d) / l } diff --git a/src/comp/fuzzy_test.go b/src/comp/fuzzy_test.go index 34e68b7..59e3988 100644 --- a/src/comp/fuzzy_test.go +++ b/src/comp/fuzzy_test.go @@ -6,55 +6,63 @@ package main import "testing" func TestFuzzy(t *testing.T) { - if d := dist("", ""); d != 0 { + var f Fuzzy + if d := f.dist("", ""); d != 0 { t.Errorf("failed (dist == %d)", d) } - if d := dist("", "a"); d != 1 { + if d := f.dist("", "a"); d != 1 { t.Errorf("failed (dist == %d)", d) } - if d := dist("a", ""); d != 1 { + if d := f.dist("a", ""); d != 1 { t.Errorf("failed (dist == %d)", d) } - if d := dist("Hello World!", "Hello World!"); d != 0 { + if d := f.dist("Hello World!", "Hello World!"); d != 0 { t.Errorf("failed (dist == %d)", d) } - if d := dist("Hello", "hEELO"); d != 5 { + if d := f.dist("Hello", "hEELO"); d != 5 { t.Errorf("failed (dist == %d)", d) } - if d := dist("Zürich", "Zurich"); d != 1 { + if d := f.dist("Zürich", "Zurich"); d != 1 { t.Errorf("failed (dist == %d)", d) } - if r := Fuzzy("", ""); r != 1 { + if r := f.Compare("", ""); r != 1 { t.Errorf("failed (fuzzy == %v)", r) } - if r := Fuzzy("", "a"); r != 0 { + if r := f.Compare("", "a"); r != 0 { t.Errorf("failed (fuzzy == %v)", r) } - if r := Fuzzy("a", ""); r != 0 { + if r := f.Compare("a", ""); r != 0 { t.Errorf("failed (fuzzy == %v)", r) } - if r := Fuzzy("Hello World!", "Hello World!"); r != 1 { + if r := f.Compare("Hello World!", "Hello World!"); r != 1 { t.Errorf("failed (fuzzy == %v)", r) } - if r := Fuzzy("Hello World!", "Hello World"); r == 0 { + if r := f.Compare("Hello World!", "Hello World"); r == 0 { t.Errorf("failed (fuzzy == %v)", r) } - if r := Fuzzy("Hello World!", "Hello wORLD?"); r != 0.5 { + if r := f.Compare("Hello World!", "Hello wORLD?"); r != 0.5 { t.Errorf("failed (fuzzy == %v)", r) } - if r := Fuzzy("Zürich", "Zurich"); r != 0.8333333333333334 { + if r := f.Compare("Zürich", "Zurich"); r != 0.8333333333333334 { t.Errorf("failed (fuzzy == %v)", r) } } + +func BenchmarkFuzzyBasic(b *testing.B) { + var f Fuzzy + for i := 0; i < b.N; i++ { + f.Compare("Hello World!", "Hello wORLD?") + } +} diff --git a/src/comp/machine.go b/src/comp/machine.go index a0b66a2..dd0d5e0 100644 --- a/src/comp/machine.go +++ b/src/comp/machine.go @@ -234,7 +234,8 @@ func (p *Program) Run(s *Stack) Value { val := p.regexps[op.Arg].MatchString(str) s.PushBool(val) case opCall: - p.funcs[op.Arg].Eval(s) + fn := p.funcs[op.Arg] + fn.Eval(fn.State, s) default: msg := fmt.Sprintf("unknown operation %v", op) panic(msg) @@ -269,6 +270,9 @@ func (p *Program) Clone(from, to int) *Program { res.regexps[i] = regexp.MustCompile(re.String()) } copy(res.funcs, p.funcs) + for i, fn := range p.funcs { + res.funcs[i].State = fn.InitState() + } return res } diff --git a/src/comp/web_test.go b/src/comp/web_test.go index 8a2acce..faec1dc 100644 --- a/src/comp/web_test.go +++ b/src/comp/web_test.go @@ -558,6 +558,7 @@ func addVars(store Store) { func init() { go func() { + log.SetOutput(ioutil.Discard) if err := Server(Port, "", runtime.NumCPU(), addVars); err != nil { log.Fatalf("failed to start comp: %v", err) }