| 1 | package bakeoff |
| 2 | |
| 3 | import ( |
| 4 | "encoding/json" |
| 5 | "fmt" |
| 6 | "sort" |
| 7 | "strings" |
| 8 | ) |
| 9 | |
| 10 | // MedianReadability combines several scorer replies: each score is the |
| 11 | // median across replies, and the notes come from the reply whose overall |
| 12 | // score is the median. Replies may wrap the JSON in a code fence. |
| 13 | func MedianReadability(replies []string) (Readability, error) { |
| 14 | type reply struct { |
| 15 | Naming, Structure, Traceability, Errors, Comments, Overall float64 |
| 16 | Notes string |
| 17 | } |
| 18 | var rs []reply |
| 19 | for _, s := range replies { |
| 20 | i, j := strings.Index(s, "{"), strings.LastIndex(s, "}") |
| 21 | if i < 0 || j < i { |
| 22 | return Readability{}, fmt.Errorf("no JSON object in reply %q", s) |
| 23 | } |
| 24 | var r reply |
| 25 | if err := json.Unmarshal([]byte(s[i:j+1]), &r); err != nil { |
| 26 | return Readability{}, err |
| 27 | } |
| 28 | rs = append(rs, r) |
| 29 | } |
| 30 | if len(rs) == 0 { |
| 31 | return Readability{}, fmt.Errorf("no replies") |
| 32 | } |
| 33 | med := func(get func(reply) float64) float64 { |
| 34 | v := make([]float64, len(rs)) |
| 35 | for i, r := range rs { |
| 36 | v[i] = get(r) |
| 37 | } |
| 38 | return median(v) |
| 39 | } |
| 40 | out := Readability{ |
| 41 | Naming: med(func(r reply) float64 { return r.Naming }), |
| 42 | Structure: med(func(r reply) float64 { return r.Structure }), |
| 43 | Traceability: med(func(r reply) float64 { return r.Traceability }), |
| 44 | Errors: med(func(r reply) float64 { return r.Errors }), |
| 45 | Comments: med(func(r reply) float64 { return r.Comments }), |
| 46 | Overall: med(func(r reply) float64 { return r.Overall }), |
| 47 | } |
| 48 | byOverall := append([]reply(nil), rs...) |
| 49 | sort.SliceStable(byOverall, func(i, j int) bool { return byOverall[i].Overall < byOverall[j].Overall }) |
| 50 | out.Notes = byOverall[len(byOverall)/2].Notes |
| 51 | return out, nil |
| 52 | } |
| 53 | |