| OLD | NEW |
| 1 // Copyright 2016 The LUCI Authors. All rights reserved. | 1 // Copyright 2016 The LUCI Authors. All rights reserved. |
| 2 // Use of this source code is governed under the Apache License, Version 2.0 | 2 // Use of this source code is governed under the Apache License, Version 2.0 |
| 3 // that can be found in the LICENSE file. | 3 // that can be found in the LICENSE file. |
| 4 | 4 |
| 5 package main | 5 package main |
| 6 | 6 |
| 7 import ( | 7 import ( |
| 8 "bytes" | 8 "bytes" |
| 9 "encoding/json" | 9 "encoding/json" |
| 10 "fmt" | |
| 11 "math" | 10 "math" |
| 12 "strings" | 11 "strings" |
| 13 | 12 |
| 14 "github.com/maruel/subcommands" | 13 "github.com/maruel/subcommands" |
| 15 ) | 14 ) |
| 16 | 15 |
| 17 var cmdEditDistance = &subcommands.Command{ | 16 var cmdEditDistance = &subcommands.Command{ |
| 18 UsageLine: `edit-distance [options]`, | 17 UsageLine: `edit-distance [options]`, |
| 19 ShortDesc: `Computes the edit distance of two strings.`, | 18 ShortDesc: `Computes the edit distance of two strings.`, |
| 20 LongDesc: `Computes the edit distance of two strings using insertion, de
letion, | 19 LongDesc: `Computes the edit distance of two strings using insertion, de
letion, |
| (...skipping 42 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 63 OpHistory string `json:"op_history"` | 62 OpHistory string `json:"op_history"` |
| 64 Error string `json:"error,omitempty"` | 63 Error string `json:"error,omitempty"` |
| 65 } | 64 } |
| 66 | 65 |
| 67 func (e *editDistanceRun) Run(a subcommands.Application, args []string) int { | 66 func (e *editDistanceRun) Run(a subcommands.Application, args []string) int { |
| 68 e.start(a, e, cmdEditDistance) | 67 e.start(a, e, cmdEditDistance) |
| 69 | 68 |
| 70 p := &EditParams{} | 69 p := &EditParams{} |
| 71 err := json.NewDecoder(bytes.NewBufferString(e.questDesc.Parameters)).De
code(p) | 70 err := json.NewDecoder(bytes.NewBufferString(e.questDesc.Parameters)).De
code(p) |
| 72 if err != nil { | 71 if err != nil { |
| 73 » » e.finish(0, "", err) | 72 » » return e.finish(EditResult{Error: err.Error()}) |
| 73 » } |
| 74 |
| 75 » rslt, stop := e.compute(p) |
| 76 » if stop { |
| 74 return 0 | 77 return 0 |
| 75 } | 78 } |
| 76 | 79 |
| 77 » e.finish(e.compute(p)) | 80 » return e.finish(rslt) |
| 78 » return 0 | |
| 79 } | 81 } |
| 80 | 82 |
| 81 var editSymbolLookup = map[string]string{ | 83 var editSymbolLookup = map[string]string{ |
| 82 "substitution": "~", | 84 "substitution": "~", |
| 83 "insertion": "+", | 85 "insertion": "+", |
| 84 "deletion": "-", | 86 "deletion": "-", |
| 85 "equality": "=", | 87 "equality": "=", |
| 86 "transposition": "X", | 88 "transposition": "X", |
| 87 } | 89 } |
| 88 | 90 |
| 89 func (e *editDistanceRun) compute(p *EditParams) (uint32, string, error) { | 91 func (e *editDistanceRun) compute(p *EditParams) (rslt EditResult, stop bool) { |
| 90 // If one string is empty, the edit distance is the length of the other | 92 // If one string is empty, the edit distance is the length of the other |
| 91 // string. | 93 // string. |
| 92 if len(p.A) == 0 { | 94 if len(p.A) == 0 { |
| 93 » » return uint32(len(p.B)), strings.Repeat("+", len(p.B)), nil | 95 » » rslt.Distance = uint32(len(p.B)) |
| 96 » » rslt.OpHistory = strings.Repeat("+", len(p.B)) |
| 97 » » return |
| 94 } else if len(p.B) == 0 { | 98 } else if len(p.B) == 0 { |
| 95 » » return uint32(len(p.A)), strings.Repeat("-", len(p.A)), nil | 99 » » rslt.Distance = uint32(len(p.A)) |
| 100 » » rslt.OpHistory = strings.Repeat("-", len(p.A)) |
| 101 » » return |
| 96 } | 102 } |
| 97 | 103 |
| 98 // If both strings are exactly equality, the distance between them is 0. | 104 // If both strings are exactly equality, the distance between them is 0. |
| 99 if p.A == p.B { | 105 if p.A == p.B { |
| 100 » » return 0, strings.Repeat("=", len(p.A)), nil | 106 » » rslt.OpHistory = strings.Repeat("=", len(p.A)) |
| 107 » » return |
| 101 } | 108 } |
| 102 | 109 |
| 103 toDep := []interface{}{ | 110 toDep := []interface{}{ |
| 104 &EditParams{ // substitution || equality | 111 &EditParams{ // substitution || equality |
| 105 A: p.A[1:], | 112 A: p.A[1:], |
| 106 B: p.B[1:], | 113 B: p.B[1:], |
| 107 }, | 114 }, |
| 108 &EditParams{ // deletion (remove a char from A) | 115 &EditParams{ // deletion (remove a char from A) |
| 109 A: p.A[1:], | 116 A: p.A[1:], |
| 110 B: p.B, | 117 B: p.B, |
| 111 }, | 118 }, |
| 112 &EditParams{ // insertion (add the char to A) | 119 &EditParams{ // insertion (add the char to A) |
| 113 A: p.A, | 120 A: p.A, |
| 114 B: p.B[1:], | 121 B: p.B[1:], |
| 115 }, | 122 }, |
| 116 } | 123 } |
| 117 if e.useTransposition && len(p.A) > 1 && len(p.B) > 1 { | 124 if e.useTransposition && len(p.A) > 1 && len(p.B) > 1 { |
| 118 » » if p.A[0] == p.B[1] && p.B[0] == p.A[1] { | 125 » » if p.A[0] == p.B[1] && p.A[1] == p.B[0] { |
| 119 toDep = append(toDep, &EditParams{ // transposition | 126 toDep = append(toDep, &EditParams{ // transposition |
| 120 A: p.A[2:], | 127 A: p.A[2:], |
| 121 B: p.B[2:], | 128 B: p.B[2:], |
| 122 }) | 129 }) |
| 123 } | 130 } |
| 124 } | 131 } |
| 125 | 132 |
| 126 opNames := []string{ | 133 opNames := []string{ |
| 127 "substitution", "deletion", "insertion", "transposition", | 134 "substitution", "deletion", "insertion", "transposition", |
| 128 } | 135 } |
| 129 if p.A[0] == p.B[0] { | 136 if p.A[0] == p.B[0] { |
| 130 opNames[0] = "equality" | 137 opNames[0] = "equality" |
| 131 } | 138 } |
| 132 | 139 |
| 133 retval := uint32(math.MaxUint32) | 140 retval := uint32(math.MaxUint32) |
| 134 opname := "" | 141 opname := "" |
| 135 opchain := "" | 142 opchain := "" |
| 136 » for i, rslt := range e.depOn(toDep...) { | 143 |
| 144 » depsData, stop := e.depOn(toDep...) |
| 145 » if stop { |
| 146 » » return |
| 147 » } |
| 148 |
| 149 » for i, dep := range depsData { |
| 137 result := &EditResult{} | 150 result := &EditResult{} |
| 138 » » err := json.NewDecoder(bytes.NewBufferString(rslt)).Decode(resul
t) | 151 » » err := json.NewDecoder(bytes.NewBufferString(dep)).Decode(result
) |
| 139 if err != nil { | 152 if err != nil { |
| 140 » » » return 0, "", err | 153 » » » rslt.Error = err.Error() |
| 154 » » » return |
| 141 } | 155 } |
| 142 | 156 |
| 143 opName := opNames[i] | 157 opName := opNames[i] |
| 144 if result.Error != "" { | 158 if result.Error != "" { |
| 145 » » » return 0, "!" + result.OpHistory, fmt.Errorf(result.Erro
r) | 159 » » » rslt.OpHistory = "!" + result.OpHistory |
| 160 » » » rslt.Error = result.Error |
| 161 » » » return |
| 146 } | 162 } |
| 147 cost := result.Distance | 163 cost := result.Distance |
| 148 if opName != "equality" { | 164 if opName != "equality" { |
| 149 // all operations (except equality) cost 1 | 165 // all operations (except equality) cost 1 |
| 150 cost++ | 166 cost++ |
| 151 } | 167 } |
| 152 if cost < retval { | 168 if cost < retval { |
| 153 retval = cost | 169 retval = cost |
| 154 opname = opName | 170 opname = opName |
| 155 opchain = result.OpHistory | 171 opchain = result.OpHistory |
| 156 } | 172 } |
| 157 } | 173 } |
| 158 | 174 |
| 159 » return retval, editSymbolLookup[opname] + opchain, nil | 175 » rslt.Distance = retval |
| 176 » rslt.OpHistory = editSymbolLookup[opname] + opchain |
| 177 » return |
| 160 } | 178 } |
| 161 | |
| 162 func (e *editDistanceRun) finish(dist uint32, opHistory string, err error) { | |
| 163 if err == nil { | |
| 164 e.cmdRun.finish(&EditResult{dist, opHistory, ""}) | |
| 165 } else { | |
| 166 e.cmdRun.finish(&EditResult{Error: err.Error()}) | |
| 167 } | |
| 168 } | |
| OLD | NEW |