func (me *mapEncoder) encode(e *encodeState, v reflect.Value, opts encOpts) { if v.IsNil() { e.WriteString("null") return } e.WriteByte('{') // Extract and sort the keys. keys := v.MapKeys() sv := make([]reflectWithString, len(keys)) for i, v := range keys { sv[i].v = v if err := sv[i].resolve(); err != nil { e.error(&MarshalerError{v.Type(), err}) } } sort.Slice(sv, func(i, j int) bool { return sv[i].s < sv[j].s }) for i, kv := range sv { if i > 0 { e.WriteByte(',') } e.string(kv.s, opts.escapeHTML) e.WriteByte(':') me.elemEnc(e, v.MapIndex(kv.v), opts) } e.WriteByte('}') }
func dirList(w ResponseWriter, f File) { dirs, err := f.Readdir(-1) if err != nil { // TODO: log err.Error() to the Server.ErrorLog, once it's possible // for a handler to get at its Server via the ResponseWriter. See // Issue 12438. Error(w, "Error reading directory", StatusInternalServerError) return } sort.Slice(dirs, func(i, j int) bool { return dirs[i].Name() < dirs[j].Name() }) w.Header().Set("Content-Type", "text/html; charset=utf-8") fmt.Fprintf(w, "<pre>\n") for _, d := range dirs { name := d.Name() if d.IsDir() { name += "/" } // name may contain '?' or '#', which must be escaped to remain // part of the URL path, and not indicate the start of a query // string or fragment. url := url.URL{Path: name} fmt.Fprintf(w, "<a href=\"%s\">%s</a>\n", url.String(), htmlReplacer.Replace(name)) } fmt.Fprintf(w, "</pre>\n") }
func nm(file string) { f, err := objfile.Open(file) if err != nil { errorf("%v", err) return } defer f.Close() syms, err := f.Symbols() if err != nil { errorf("reading %s: %v", file, err) } if len(syms) == 0 { errorf("reading %s: no symbols", file) } switch *sortOrder { case "address": sort.Slice(syms, func(i, j int) bool { return syms[i].Addr < syms[j].Addr }) case "name": sort.Slice(syms, func(i, j int) bool { return syms[i].Name < syms[j].Name }) case "size": sort.Slice(syms, func(i, j int) bool { return syms[i].Size > syms[j].Size }) } w := bufio.NewWriter(os.Stdout) for _, sym := range syms { if filePrefix { fmt.Fprintf(w, "%s:\t", file) } if sym.Code == 'U' { fmt.Fprintf(w, "%8s", "") } else { fmt.Fprintf(w, "%8x", sym.Addr) } if *printSize { fmt.Fprintf(w, " %10d", sym.Size) } fmt.Fprintf(w, " %c %s", sym.Code, sym.Name) if *printType && sym.Type != "" { fmt.Fprintf(w, " %s", sym.Type) } fmt.Fprintf(w, "\n") } w.Flush() }
func ExampleSlice() { people := []struct { Name string Age int }{ {"Gopher", 7}, {"Alice", 55}, {"Vera", 24}, {"Bob", 75}, } sort.Slice(people, func(i, j int) bool { return people[i].Name < people[j].Name }) fmt.Println("By name:", people) sort.Slice(people, func(i, j int) bool { return people[i].Age < people[j].Age }) fmt.Println("By age:", people) // Output: By name: [{Alice 55} {Bob 75} {Gopher 7} {Vera 24}] // By age: [{Gopher 7} {Vera 24} {Alice 55} {Bob 75}] }
func (d *MultiDialer) pickupTLSAddrs(addrs []string, n int) []string { if len(addrs) <= n { return addrs } type racer struct { addr string duration time.Duration } goodAddrs := make([]racer, 0) unknownAddrs := make([]string, 0) badAddrs := make([]string, 0) for _, addr := range addrs { if duration, ok := d.TLSConnDuration.GetNotStale(addr); ok { if d, ok := duration.(time.Duration); !ok { glog.Errorf("%#v for %#v is not a time.Duration", duration, addr) } else { goodAddrs = append(goodAddrs, racer{addr, d}) } } else if e, ok := d.TLSConnError.GetNotStale(addr); ok { if _, ok := e.(error); !ok { glog.Errorf("%#v for %#v is not a error", e, addr) } else { badAddrs = append(badAddrs, addr) } } else { unknownAddrs = append(unknownAddrs, addr) } } addrs1 := make([]string, 0, n) sort.Slice(goodAddrs, func(i, j int) bool { return goodAddrs[i].duration < goodAddrs[j].duration }) if len(goodAddrs) > n/2 { goodAddrs = goodAddrs[:n/2] } for _, r := range goodAddrs { addrs1 = append(addrs1, r.addr) } for _, addrs2 := range [][]string{unknownAddrs, badAddrs} { if len(addrs1) < n && len(addrs2) > 0 { m := n - len(addrs1) if len(addrs2) > m { ShuffleStringsN(addrs2, m) addrs2 = addrs2[:m] } addrs1 = append(addrs1, addrs2...) } } return addrs1 }
// Profiles returns a slice of all the known profiles, sorted by name. func Profiles() []*Profile { lockProfiles() defer unlockProfiles() all := make([]*Profile, 0, len(profiles.m)) for _, p := range profiles.m { all = append(all, p) } sort.Slice(all, func(i, j int) bool { return all[i].name < all[j].name }) return all }
// ReadGCStats reads statistics about garbage collection into stats. // The number of entries in the pause history is system-dependent; // stats.Pause slice will be reused if large enough, reallocated otherwise. // ReadGCStats may use the full capacity of the stats.Pause slice. // If stats.PauseQuantiles is non-empty, ReadGCStats fills it with quantiles // summarizing the distribution of pause time. For example, if // len(stats.PauseQuantiles) is 5, it will be filled with the minimum, // 25%, 50%, 75%, and maximum pause times. func ReadGCStats(stats *GCStats) { // Create a buffer with space for at least two copies of the // pause history tracked by the runtime. One will be returned // to the caller and the other will be used as transfer buffer // for end times history and as a temporary buffer for // computing quantiles. const maxPause = len(((*runtime.MemStats)(nil)).PauseNs) if cap(stats.Pause) < 2*maxPause+3 { stats.Pause = make([]time.Duration, 2*maxPause+3) } // readGCStats fills in the pause and end times histories (up to // maxPause entries) and then three more: Unix ns time of last GC, // number of GC, and total pause time in nanoseconds. Here we // depend on the fact that time.Duration's native unit is // nanoseconds, so the pauses and the total pause time do not need // any conversion. readGCStats(&stats.Pause) n := len(stats.Pause) - 3 stats.LastGC = time.Unix(0, int64(stats.Pause[n])) stats.NumGC = int64(stats.Pause[n+1]) stats.PauseTotal = stats.Pause[n+2] n /= 2 // buffer holds pauses and end times stats.Pause = stats.Pause[:n] if cap(stats.PauseEnd) < maxPause { stats.PauseEnd = make([]time.Time, 0, maxPause) } stats.PauseEnd = stats.PauseEnd[:0] for _, ns := range stats.Pause[n : n+n] { stats.PauseEnd = append(stats.PauseEnd, time.Unix(0, int64(ns))) } if len(stats.PauseQuantiles) > 0 { if n == 0 { for i := range stats.PauseQuantiles { stats.PauseQuantiles[i] = 0 } } else { // There's room for a second copy of the data in stats.Pause. // See the allocation at the top of the function. sorted := stats.Pause[n : n+n] copy(sorted, stats.Pause) sort.Slice(sorted, func(i, j int) bool { return sorted[i] < sorted[j] }) nq := len(stats.PauseQuantiles) - 1 for i := 0; i < nq; i++ { stats.PauseQuantiles[i] = sorted[len(sorted)*i/nq] } stats.PauseQuantiles[nq] = sorted[len(sorted)-1] } } }
// ReadDir reads the directory named by dirname and returns // a list of directory entries sorted by filename. func ReadDir(dirname string) ([]os.FileInfo, error) { f, err := os.Open(dirname) if err != nil { return nil, err } list, err := f.Readdir(-1) f.Close() if err != nil { return nil, err } sort.Slice(list, func(i, j int) bool { return list[i].Name() < list[j].Name() }) return list, nil }
func (t *testFuncs) load(filename, pkg string, doImport, seen *bool) error { f, err := parser.ParseFile(testFileSet, filename, nil, parser.ParseComments) if err != nil { return expandScanner(err) } for _, d := range f.Decls { n, ok := d.(*ast.FuncDecl) if !ok { continue } if n.Recv != nil { continue } name := n.Name.String() switch { case name == "TestMain" && isTestFunc(n, "M"): if t.TestMain != nil { return errors.New("multiple definitions of TestMain") } t.TestMain = &testFunc{pkg, name, "", false} *doImport, *seen = true, true case isTest(name, "Test"): err := checkTestFunc(n, "T") if err != nil { return err } t.Tests = append(t.Tests, testFunc{pkg, name, "", false}) *doImport, *seen = true, true case isTest(name, "Benchmark"): err := checkTestFunc(n, "B") if err != nil { return err } t.Benchmarks = append(t.Benchmarks, testFunc{pkg, name, "", false}) *doImport, *seen = true, true } } ex := doc.Examples(f) sort.Slice(ex, func(i, j int) bool { return ex[i].Order < ex[j].Order }) for _, e := range ex { *doImport = true // import test file whether executed or not if e.Output == "" && !e.EmptyOutput { // Don't run examples with no output. continue } t.Examples = append(t.Examples, testFunc{pkg, "Example" + e.Name, e.Output, e.Unordered}) *seen = true } return nil }
// writeMutex writes the current mutex profile to w. func writeMutex(w io.Writer, debug int) error { // TODO(pjw): too much common code with writeBlock. FIX! var p []runtime.BlockProfileRecord n, ok := runtime.MutexProfile(nil) for { p = make([]runtime.BlockProfileRecord, n+50) n, ok = runtime.MutexProfile(p) if ok { p = p[:n] break } } sort.Slice(p, func(i, j int) bool { return p[i].Cycles > p[j].Cycles }) b := bufio.NewWriter(w) var tw *tabwriter.Writer w = b if debug > 0 { tw = tabwriter.NewWriter(w, 1, 8, 1, '\t', 0) w = tw } fmt.Fprintf(w, "--- mutex:\n") fmt.Fprintf(w, "cycles/second=%v\n", runtime_cyclesPerSecond()) fmt.Fprintf(w, "sampling period=%d\n", runtime.SetMutexProfileFraction(-1)) for i := range p { r := &p[i] fmt.Fprintf(w, "%v %v @", r.Cycles, r.Count) for _, pc := range r.Stack() { fmt.Fprintf(w, " %#x", pc) } fmt.Fprint(w, "\n") if debug > 0 { printStackRecord(w, r.Stack(), true) } } if tw != nil { tw.Flush() } return b.Flush() }
// writeBlock writes the current blocking profile to w. func writeBlock(w io.Writer, debug int) error { var p []runtime.BlockProfileRecord n, ok := runtime.BlockProfile(nil) for { p = make([]runtime.BlockProfileRecord, n+50) n, ok = runtime.BlockProfile(p) if ok { p = p[:n] break } } sort.Slice(p, func(i, j int) bool { return p[i].Cycles > p[j].Cycles }) b := bufio.NewWriter(w) var tw *tabwriter.Writer w = b if debug > 0 { tw = tabwriter.NewWriter(w, 1, 8, 1, '\t', 0) w = tw } fmt.Fprintf(w, "--- contention:\n") fmt.Fprintf(w, "cycles/second=%v\n", runtime_cyclesPerSecond()) for i := range p { r := &p[i] fmt.Fprintf(w, "%v %v @", r.Cycles, r.Count) for _, pc := range r.Stack() { fmt.Fprintf(w, " %#x", pc) } fmt.Fprint(w, "\n") if debug > 0 { printStackRecord(w, r.Stack(), true) } } if tw != nil { tw.Flush() } return b.Flush() }
// writeBlock writes the current blocking profile to w. func writeBlock(w io.Writer, debug int) error { var p []runtime.BlockProfileRecord n, ok := runtime.BlockProfile(nil) for { // Code by analogy with writeBlock func p = make([]runtime.BlockProfileRecord, n+50) n, ok = runtime.BlockProfile(p) if ok { p = p[:n] break } } sort.Slice(p, func(i, j int) bool { return p[i].Cycles > p[j].Cycles }) prof := &profile.Profile{ PeriodType: &profile.ValueType{Type: "contentions", Unit: "count"}, Period: 1, SampleType: []*profile.ValueType{ {Type: "contentions", Unit: "count"}, {Type: "delay", Unit: "nanoseconds"}, }, } cpuHz := runtime_cyclesPerSecond() locs := make(map[uint64]*profile.Location) for i := range p { r := &p[i] var v1, v2 int64 v1 = r.Cycles v2 = r.Count if prof.Period > 0 { if cpuHz > 0 { cpuGHz := float64(cpuHz) / 1e9 v1 = int64(float64(v1) * float64(prof.Period) / cpuGHz) } v2 = v2 * prof.Period } value := []int64{v2, v1} var sloc []*profile.Location for _, pc := range r.Stack() { addr := uint64(pc) addr-- loc := locs[addr] if locs[addr] == nil { loc = &profile.Location{ Address: addr, } prof.Location = append(prof.Location, loc) locs[addr] = loc } sloc = append(sloc, loc) } prof.Sample = append(prof.Sample, &profile.Sample{ Value: value, Location: sloc, }) } prof.RemapAll() protopprof.Symbolize(prof) return prof.Write(w) }
// writeHeap writes the current runtime heap profile to w. func writeHeap(w io.Writer, debug int) error { // Find out how many records there are (MemProfile(nil, true)), // allocate that many records, and get the data. // There's a race—more records might be added between // the two calls—so allocate a few extra records for safety // and also try again if we're very unlucky. // The loop should only execute one iteration in the common case. var p []runtime.MemProfileRecord n, ok := runtime.MemProfile(nil, true) for { p = make([]runtime.MemProfileRecord, n+50) n, ok = runtime.MemProfile(p, true) if ok { p = p[0:n] break } } sort.Slice(p, func(i, j int) bool { return p[i].InUseBytes() > p[j].InUseBytes() }) var total runtime.MemProfileRecord for i := range p { r := &p[i] total.AllocBytes += r.AllocBytes total.AllocObjects += r.AllocObjects total.FreeBytes += r.FreeBytes total.FreeObjects += r.FreeObjects } prof := &profile.Profile{ PeriodType: &profile.ValueType{Type: "space", Unit: "bytes"}, SampleType: []*profile.ValueType{ {Type: "alloc_objects", Unit: "count"}, {Type: "alloc_space", Unit: "bytes"}, {Type: "inuse_objects", Unit: "count"}, {Type: "inuse_space", Unit: "bytes"}, }, Period: int64(runtime.MemProfileRate), } locs := make(map[uint64]*(profile.Location)) for i := range p { var v1, v2, v3, v4, blocksize int64 r := &p[i] v1, v2 = int64(r.InUseObjects()), int64(r.InUseBytes()) v3, v4 = int64(r.AllocObjects), int64(r.AllocBytes) if (v1 == 0 && v2 != 0) || (v3 == 0 && v4 != 0) { return fmt.Errorf("error writing memory profile: inuse object count was 0 but inuse bytes was %d", v2) } else { if v1 != 0 { blocksize = v2 / v1 v1, v2 = scaleHeapSample(v1, v2, prof.Period) } if v3 != 0 { v3, v4 = scaleHeapSample(v3, v4, prof.Period) } } value := []int64{v1, v2, v3, v4} var sloc []*profile.Location for _, pc := range r.Stack() { addr := uint64(pc) addr-- loc := locs[addr] if locs[addr] == nil { loc = &(profile.Location{ Address: addr, }) prof.Location = append(prof.Location, loc) locs[addr] = loc } sloc = append(sloc, loc) } prof.Sample = append(prof.Sample, &profile.Sample{ Value: value, Location: sloc, NumLabel: map[string][]int64{"bytes": {blocksize}}, }) } prof.RemapAll() protopprof.Symbolize(prof) return prof.Write(w) }
// writeHeap writes the current runtime heap profile to w. func writeHeap(w io.Writer, debug int) error { // Find out how many records there are (MemProfile(nil, true)), // allocate that many records, and get the data. // There's a race—more records might be added between // the two calls—so allocate a few extra records for safety // and also try again if we're very unlucky. // The loop should only execute one iteration in the common case. var p []runtime.MemProfileRecord n, ok := runtime.MemProfile(nil, true) for { // Allocate room for a slightly bigger profile, // in case a few more entries have been added // since the call to MemProfile. p = make([]runtime.MemProfileRecord, n+50) n, ok = runtime.MemProfile(p, true) if ok { p = p[0:n] break } // Profile grew; try again. } if debug == 0 { pp := protopprof.EncodeMemProfile(p, int64(runtime.MemProfileRate), time.Now()) return pp.Write(w) } sort.Slice(p, func(i, j int) bool { return p[i].InUseBytes() > p[j].InUseBytes() }) b := bufio.NewWriter(w) tw := tabwriter.NewWriter(b, 1, 8, 1, '\t', 0) w = tw var total runtime.MemProfileRecord for i := range p { r := &p[i] total.AllocBytes += r.AllocBytes total.AllocObjects += r.AllocObjects total.FreeBytes += r.FreeBytes total.FreeObjects += r.FreeObjects } // Technically the rate is MemProfileRate not 2*MemProfileRate, // but early versions of the C++ heap profiler reported 2*MemProfileRate, // so that's what pprof has come to expect. fmt.Fprintf(w, "heap profile: %d: %d [%d: %d] @ heap/%d\n", total.InUseObjects(), total.InUseBytes(), total.AllocObjects, total.AllocBytes, 2*runtime.MemProfileRate) for i := range p { r := &p[i] fmt.Fprintf(w, "%d: %d [%d: %d] @", r.InUseObjects(), r.InUseBytes(), r.AllocObjects, r.AllocBytes) for _, pc := range r.Stack() { fmt.Fprintf(w, " %#x", pc) } fmt.Fprintf(w, "\n") printStackRecord(w, r.Stack(), false) } // Print memstats information too. // Pprof will ignore, but useful for people s := new(runtime.MemStats) runtime.ReadMemStats(s) fmt.Fprintf(w, "\n# runtime.MemStats\n") fmt.Fprintf(w, "# Alloc = %d\n", s.Alloc) fmt.Fprintf(w, "# TotalAlloc = %d\n", s.TotalAlloc) fmt.Fprintf(w, "# Sys = %d\n", s.Sys) fmt.Fprintf(w, "# Lookups = %d\n", s.Lookups) fmt.Fprintf(w, "# Mallocs = %d\n", s.Mallocs) fmt.Fprintf(w, "# Frees = %d\n", s.Frees) fmt.Fprintf(w, "# HeapAlloc = %d\n", s.HeapAlloc) fmt.Fprintf(w, "# HeapSys = %d\n", s.HeapSys) fmt.Fprintf(w, "# HeapIdle = %d\n", s.HeapIdle) fmt.Fprintf(w, "# HeapInuse = %d\n", s.HeapInuse) fmt.Fprintf(w, "# HeapReleased = %d\n", s.HeapReleased) fmt.Fprintf(w, "# HeapObjects = %d\n", s.HeapObjects) fmt.Fprintf(w, "# Stack = %d / %d\n", s.StackInuse, s.StackSys) fmt.Fprintf(w, "# MSpan = %d / %d\n", s.MSpanInuse, s.MSpanSys) fmt.Fprintf(w, "# MCache = %d / %d\n", s.MCacheInuse, s.MCacheSys) fmt.Fprintf(w, "# BuckHashSys = %d\n", s.BuckHashSys) fmt.Fprintf(w, "# GCSys = %d\n", s.GCSys) fmt.Fprintf(w, "# OtherSys = %d\n", s.OtherSys) fmt.Fprintf(w, "# NextGC = %d\n", s.NextGC) fmt.Fprintf(w, "# PauseNs = %d\n", s.PauseNs) fmt.Fprintf(w, "# NumGC = %d\n", s.NumGC) fmt.Fprintf(w, "# DebugGC = %v\n", s.DebugGC) tw.Flush() return b.Flush() }
// cookies is like Cookies but takes the current time as a parameter. func (j *Jar) cookies(u *url.URL, now time.Time) (cookies []*http.Cookie) { if u.Scheme != "http" && u.Scheme != "https" { return cookies } host, err := canonicalHost(u.Host) if err != nil { return cookies } key := jarKey(host, j.psList) j.mu.Lock() defer j.mu.Unlock() submap := j.entries[key] if submap == nil { return cookies } https := u.Scheme == "https" path := u.Path if path == "" { path = "/" } modified := false var selected []entry for id, e := range submap { if e.Persistent && !e.Expires.After(now) { delete(submap, id) modified = true continue } if !e.shouldSend(https, host, path) { continue } e.LastAccess = now submap[id] = e selected = append(selected, e) modified = true } if modified { if len(submap) == 0 { delete(j.entries, key) } else { j.entries[key] = submap } } // sort according to RFC 6265 section 5.4 point 2: by longest // path and then by earliest creation time. sort.Slice(selected, func(i, j int) bool { s := selected if len(s[i].Path) != len(s[j].Path) { return len(s[i].Path) > len(s[j].Path) } if !s[i].Creation.Equal(s[j].Creation) { return s[i].Creation.Before(s[j].Creation) } return s[i].seqNum < s[j].seqNum }) for _, e := range selected { cookies = append(cookies, &http.Cookie{Name: e.Name, Value: e.Value}) } return cookies }
// typeFields returns a list of fields that JSON should recognize for the given type. // The algorithm is breadth-first search over the set of structs to include - the top struct // and then any reachable anonymous structs. func typeFields(t reflect.Type) []field { // Anonymous fields to explore at the current level and the next. current := []field{} next := []field{{typ: t}} // Count of queued names for current level and the next. count := map[reflect.Type]int{} nextCount := map[reflect.Type]int{} // Types already visited at an earlier level. visited := map[reflect.Type]bool{} // Fields found. var fields []field for len(next) > 0 { current, next = next, current[:0] count, nextCount = nextCount, map[reflect.Type]int{} for _, f := range current { if visited[f.typ] { continue } visited[f.typ] = true // Scan f.typ for fields to include. for i := 0; i < f.typ.NumField(); i++ { sf := f.typ.Field(i) if sf.PkgPath != "" && !sf.Anonymous { // unexported continue } tag := sf.Tag.Get("json") if tag == "-" { continue } name, opts := parseTag(tag) if !isValidTag(name) { name = "" } index := make([]int, len(f.index)+1) copy(index, f.index) index[len(f.index)] = i ft := sf.Type if ft.Name() == "" && ft.Kind() == reflect.Ptr { // Follow pointer. ft = ft.Elem() } // Only strings, floats, integers, and booleans can be quoted. quoted := false if opts.Contains("string") { switch ft.Kind() { case reflect.Bool, reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Float32, reflect.Float64, reflect.String: quoted = true } } // Record found field and index sequence. if name != "" || !sf.Anonymous || ft.Kind() != reflect.Struct { tagged := name != "" if name == "" { name = sf.Name } fields = append(fields, fillField(field{ name: name, tag: tagged, index: index, typ: ft, omitEmpty: opts.Contains("omitempty"), quoted: quoted, })) if count[f.typ] > 1 { // If there were multiple instances, add a second, // so that the annihilation code will see a duplicate. // It only cares about the distinction between 1 or 2, // so don't bother generating any more copies. fields = append(fields, fields[len(fields)-1]) } continue } // Record new anonymous struct to explore in next round. nextCount[ft]++ if nextCount[ft] == 1 { next = append(next, fillField(field{name: ft.Name(), index: index, typ: ft})) } } } } sort.Slice(fields, func(i, j int) bool { x := fields // sort field by name, breaking ties with depth, then // breaking ties with "name came from json tag", then // breaking ties with index sequence. if x[i].name != x[j].name { return x[i].name < x[j].name } if len(x[i].index) != len(x[j].index) { return len(x[i].index) < len(x[j].index) } if x[i].tag != x[j].tag { return x[i].tag } return byIndex(x).Less(i, j) }) // Delete all fields that are hidden by the Go rules for embedded fields, // except that fields with JSON tags are promoted. // The fields are sorted in primary order of name, secondary order // of field index length. Loop over names; for each name, delete // hidden fields by choosing the one dominant field that survives. out := fields[:0] for advance, i := 0, 0; i < len(fields); i += advance { // One iteration per name. // Find the sequence of fields with the name of this first field. fi := fields[i] name := fi.name for advance = 1; i+advance < len(fields); advance++ { fj := fields[i+advance] if fj.name != name { break } } if advance == 1 { // Only one field with this name out = append(out, fi) continue } dominant, ok := dominantField(fields[i : i+advance]) if ok { out = append(out, dominant) } } fields = out sort.Sort(byIndex(fields)) return fields }