├── LICENCE ├── README.md ├── cfmagic.go ├── configLoader.go ├── configs.json ├── configvalue.go ├── individual.go ├── magic.go └── population.go /LICENCE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Ben Falconer 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # cfmagic 2 | 3 | Clang is cool and Clang-Format is really cool, so adding genetic algorithms makes it super cool __and__ hype. 4 | 5 | [![asciicast](https://asciinema.org/a/232254.svg)](https://asciinema.org/a/232254) 6 | 7 | ## Why ? 8 | 9 | Clang-Format is a great tool when you want to make sure your team uses the proper indent conventions so that your code base looks homogenous. 10 | Unfortunately, you will soon realize that there are plenty of clang-format configuration parameters, which will frequently interact with each other. 11 | 12 | You usually end up changing randomly some settings that might fix your problem for several hours. 13 | 14 | When you have no real idea of how you should handle such a problem but the two following rules can be observed, genetic algorithms are a proper choice: 15 | 16 | - You can easily score a result 17 | - Mixing two good solutions may give you a better one 18 | 19 | 20 | ## How? 21 | 22 | Basically cfmagic will apply the following algorithm: 23 | 24 | 1. Generate a set of random .clang-format configuration files 25 | 2. Apply clang-format with these config files on a piece of code properly indented according to your standards 26 | 3. Compute the difference between the original piece of code and the re-formatted one and establish a 'score' 27 | 4. Drop those with too many differences (high score) 28 | 5. Mix the others to regenerate new ones (and add some randomness: mutations) 29 | 6. GOTO 2 30 | 31 | 32 | ## Isn't it overkill? 33 | 34 | Yes probably, but that's fun and actually is does work pretty well! 35 | 36 | ## How do I use this hype-overkill-wizardry? 37 | 38 | __Build:__ 39 | 40 | ```bash 41 | go build 42 | ``` 43 | 44 | Take a piece of code and format it manually according to your standards. 45 | __Run:__ 46 | 47 | ```bash 48 | ./cfmagic `` mypieceofcode.c `` `` 49 | ``` 50 | 51 | You may want to adapt: 52 | 53 | - ``: (ex.: /usr/bin/clang-format-6.0) the path of your locally installed clang-format binary 54 | - ``: (ex.: 20) the size of the population (ie.: the number of randomly generated config files). A number between 10 and 100 is nice. 55 | - ``: (ex: 14) the mutation rate. A number between 1 and 20 is nice 56 | 57 | ## Credits 58 | 59 | The __configs.json__ file has been taken from [https://github.com/zed0/clang-format-configurator](https://github.com/zed0/clang-format-configurator), all credits to him. 60 | 61 | ## FAQ 62 | 63 | __How long does it take to find a nice .clang-format configuration file matching my crazy indentation style?__ 64 | 65 | A few minutes is usually enough. 66 | 67 | __It's been running for three weeks, how do I get the results now?__ 68 | 69 | Just hit ctrl+C, the current best solution will be written in a .clang-format file. 70 | 71 | __Will cfmagic always find the ultimate .clang-format configuration file?__ 72 | 73 | No, simply because all possibilities are not managed by clang-format. cfmagic will find a "close" solution. 74 | 75 | In addition to this, genetic algorithm do not necessarily find the best possible solution. 76 | 77 | __What about tuning population size and mutation rate parameters?__ 78 | 79 | I find out that using a large population (ex. 100) seems to be counter productive. 80 | Mutation rate can be tuned, but adding too much randomness (> 20) doesn't make any sense. 81 | 82 | __I found your mutation boost secret feature!__ 83 | 84 | After a certain number of generations (ie. iterations), you may end up with a set of individuals (ie. clang-format configs) very close to each other. 85 | At this point, you will want to renew things by adding more randomness. 86 | 87 | In order to do so, we compute the standard deviation of the scores for every generation. If too low, mutationRate is doubled for the next generation. 88 | 89 | __I see "signal: floating point exception" errors__ 90 | 91 | Funnily enough, cfmagic also acts as a clang-format fuzzer! 92 | So "yes", some versions of clang-format may crash: this is handled by cfmagic and has no impact on the results (you may safely ignore these errors). 93 | 94 | -------------------------------------------------------------------------------- /cfmagic.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "io/ioutil" 6 | "math" 7 | "os" 8 | "os/signal" 9 | "runtime" 10 | "strconv" 11 | "syscall" 12 | ) 13 | 14 | // JSON taken from https://zed0.co.uk/clang-format-configurator/doc/ 15 | 16 | const configs_file = "configs.json" 17 | const minStdDevForMutationBoost = 5.0 18 | const scoreUnitialized = math.MaxUint32 19 | 20 | type ConfigEntry struct { 21 | Type string `json:"type"` 22 | Doc string `json:"doc"` 23 | Options []string `json:"options"` 24 | } 25 | 26 | func main() { 27 | var clangPath string 28 | var perfectSource string 29 | if len(os.Args) != 5 { 30 | fmt.Println("Usage: " + os.Args[0] + " ") 31 | fmt.Println("Example: " + os.Args[0] + "/usr/bin/clang-format-6.0 perfect.c 20 4") 32 | return 33 | } 34 | clangPath = os.Args[1] 35 | perfectSource = os.Args[2] 36 | pop, err := strconv.Atoi(os.Args[3]) 37 | if err != nil { 38 | fmt.Println("Error : invalid population size (default: 100)") 39 | return 40 | } 41 | mut, err := strconv.Atoi(os.Args[4]) 42 | if err != nil { 43 | fmt.Println("Error : invalid mutation rate (default: 4)") 44 | return 45 | } 46 | 47 | //fmt.Println(entries) 48 | entries, err := loadConfig(clangPath) 49 | if err != nil { 50 | fmt.Println(err) 51 | return 52 | } 53 | 54 | cfmagic(clangPath, entries, perfectSource, uint32(pop), uint32(mut)) 55 | } 56 | 57 | func evalPopulation(clangPath string, perfectSourceData []byte, pop *Population, fromIdx int, toIdx int, doneCH chan int) error { 58 | for i := fromIdx; i < toIdx; i++ { 59 | if pop.population[i].score != scoreUnitialized { 60 | continue 61 | } 62 | err := pop.population[i].UpdateScore(clangPath, perfectSourceData) 63 | if err != nil { 64 | fmt.Println(err) 65 | pop.population[i].score = scoreUnitialized 66 | } 67 | } 68 | doneCH <- 1 69 | return nil 70 | } 71 | 72 | func cfmagic(clangPath string, configEntries map[string]*ConfigEntry, perfectSource string, populationSize uint32, mutationRate uint32) { 73 | if (populationSize < 2) || (mutationRate > 100) { 74 | fmt.Println("Invalid settings") 75 | return 76 | } 77 | 78 | perfectSourceData, err := ioutil.ReadFile(perfectSource) 79 | if err != nil { 80 | fmt.Println(err) 81 | return 82 | } 83 | 84 | pop := genPopulation(populationSize, configEntries) 85 | fmt.Printf("Population size : %d | Mutation rate %d %%\n", populationSize, mutationRate) 86 | 87 | done := false 88 | sigc := make(chan os.Signal, 1) 89 | signal.Notify(sigc, 90 | syscall.SIGHUP, 91 | syscall.SIGINT, 92 | syscall.SIGTERM, 93 | syscall.SIGQUIT) 94 | go func() { 95 | s := <-sigc 96 | fmt.Println("Received signal " + s.String()) 97 | done = true 98 | }() 99 | 100 | fmt.Println("Hit CTRL+C to end iterations and save the current best result") 101 | threads := runtime.NumCPU() - 1 102 | if threads == 0 { 103 | threads = 1 104 | } 105 | 106 | threads = 1 107 | 108 | fmt.Printf("Will use %d threads\n", threads) 109 | chunks := len(pop.population) / threads 110 | for { 111 | pop.generation++ 112 | 113 | for z := 0; z < len(pop.population); z++ { 114 | pop.population[z].score = scoreUnitialized 115 | } 116 | //Eval everyone 117 | doneCh := make(chan int, threads) 118 | for t := 0; t < threads; t++ { 119 | var from int 120 | var to int 121 | from = t * chunks 122 | if t == threads-1 { 123 | to = len(pop.population) 124 | } else { 125 | to = (t + 1) * chunks 126 | } 127 | 128 | go evalPopulation(clangPath, perfectSourceData, &pop, from, to, doneCh) 129 | } 130 | // Wait for all goroutines to exit 131 | for i := 0; i < threads; i++ { 132 | _ = <-doneCh 133 | } 134 | 135 | //Sort 136 | pop.sort() 137 | stdDev := pop.getStdDev(len(pop.population) / 2) 138 | fmt.Printf("Best score for generation %d: %d (stdDev: %f)\n", pop.generation, pop.population[0].score, stdDev) 139 | for z := 0; z < len(pop.population); z++ { 140 | fmt.Printf("#%d score : %d\n", z, pop.population[z].score) 141 | } 142 | if pop.population[0].score == 0 { 143 | fmt.Println("Found perfect configuration, stopping here.") 144 | break 145 | } 146 | 147 | //Mix 148 | //Boost mutations if top population is too homogenous 149 | if stdDev < minStdDevForMutationBoost { 150 | pop.mix(2*mutationRate, configEntries) 151 | } else { 152 | pop.mix(mutationRate, configEntries) 153 | } 154 | if done == true { 155 | break 156 | } 157 | } 158 | 159 | fmt.Printf("Best configuration has score: %d\n", pop.population[0].score) 160 | ioutil.WriteFile(".clang-format", []byte(pop.population[0].toClangFormatConfigFile()), os.ModePerm) 161 | fmt.Println("Written to .clang-format") 162 | } 163 | -------------------------------------------------------------------------------- /configLoader.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/json" 5 | "errors" 6 | "fmt" 7 | "io/ioutil" 8 | "log" 9 | "os/exec" 10 | "strings" 11 | ) 12 | 13 | func getClangFormatVersion(clangCmd string) string { 14 | 15 | out, err := exec.Command(clangCmd, "-version").Output() 16 | if err != nil { 17 | log.Fatal(err) 18 | } 19 | 20 | pos := strings.Index(string(out), "version ") 21 | if pos == -1 { 22 | return "" 23 | } 24 | 25 | version := string(out)[pos+8:] 26 | 27 | pos = strings.Index(version, " ") 28 | if pos == -1 { 29 | return "" 30 | } 31 | 32 | version = version[:pos] 33 | return version 34 | } 35 | 36 | func loadConfig(clangPath string) (map[string]*ConfigEntry, error) { 37 | 38 | var entries map[string]*ConfigEntry 39 | 40 | //Parse clang version 41 | version := getClangFormatVersion(clangPath) 42 | if len(version) == 0 { 43 | return nil, errors.New("Failed to fetch clang version") 44 | } 45 | fmt.Println("Using clang version: " + version) 46 | configsData, err := ioutil.ReadFile(configs_file) 47 | if err != nil { 48 | return nil, errors.New("Failed to load " + configs_file) 49 | } 50 | 51 | //Read clang configs : load as raw json (just extract keys and store internal value for later parsing) 52 | var data map[string]json.RawMessage 53 | err = json.Unmarshal(configsData, &data) 54 | if err != nil { 55 | return nil, errors.New("Failed to parse " + configs_file) 56 | } 57 | 58 | //Parse Values for version key 59 | var availableVersions []string 60 | err = json.Unmarshal(data["versions"], &availableVersions) 61 | if err != nil { 62 | return nil, errors.New("Failed to parse " + configs_file) 63 | } 64 | 65 | //Match with current local version 66 | configVersionEntry := "" 67 | for _, v := range availableVersions { 68 | if strings.Index(version, v) == 0 { 69 | configVersionEntry = v 70 | break 71 | } 72 | } 73 | if len(configVersionEntry) == 0 { 74 | fmt.Println("Failed to match version " + version + " with known versions, will use HEAD instead") 75 | configVersionEntry = "HEAD" 76 | } 77 | fmt.Println("Will be using configuration settings for version " + configVersionEntry) 78 | 79 | //Now parse keys for given version 80 | configVersionitems := data[configVersionEntry] 81 | 82 | err = json.Unmarshal(configVersionitems, &entries) 83 | if err != nil { 84 | return nil, errors.New("Failed to parse " + configs_file) 85 | } 86 | 87 | for k, v := range entries { 88 | fmt.Print(k + ": " + v.Type) 89 | if len(v.Options) != 0 { 90 | fmt.Print(" Options:") 91 | fmt.Print(v.Options) 92 | } 93 | fmt.Println("") 94 | } 95 | 96 | return entries, nil 97 | } 98 | -------------------------------------------------------------------------------- /configvalue.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "math/rand" 6 | "strings" 7 | ) 8 | 9 | const intRange = 120 10 | 11 | var ignoredSettings = map[string]bool{ 12 | "Language": true, 13 | "DisableFormat": true, 14 | "BreakAfterJavaFieldAnnotations": true, 15 | } 16 | 17 | type ConfigEntryType int 18 | 19 | const ( 20 | String ConfigEntryType = iota 21 | Int 22 | Unsigned 23 | Bool 24 | Flags 25 | ) 26 | 27 | type ConfigValue struct { 28 | entry string 29 | valueType ConfigEntryType 30 | 31 | valueString string 32 | valueBool bool 33 | valueInt int 34 | valueUnsigned uint32 35 | valueFlags map[string]bool 36 | } 37 | 38 | func (cv ConfigValue) String() (s string) { 39 | switch cv.valueType { 40 | case String: 41 | return cv.entry + ": " + cv.valueString 42 | case Int: 43 | return cv.entry + ": " + fmt.Sprintf("%d", cv.valueInt) 44 | case Unsigned: 45 | return cv.entry + ": " + fmt.Sprintf("%d", cv.valueUnsigned) 46 | case Bool: 47 | return cv.entry + ": " + fmt.Sprintf("%t", cv.valueBool) 48 | case Flags: 49 | s = cv.entry + ": {" 50 | idx := 0 51 | for f, v := range cv.valueFlags { 52 | if idx != 0 { 53 | s += ", " 54 | } 55 | idx++ 56 | 57 | s += f + ": " + fmt.Sprintf("%t", v) 58 | } 59 | s += "}" 60 | return s 61 | } 62 | 63 | return "" 64 | } 65 | 66 | func generateConfigValue(entry string, configEntries map[string]*ConfigEntry) *ConfigValue { 67 | var cv ConfigValue 68 | cv.entry = entry 69 | 70 | found, _ := ignoredSettings[entry] 71 | if found == true { 72 | return nil 73 | } 74 | 75 | v := configEntries[entry] 76 | if strings.LastIndex(v.Type, "Flags") != -1 { 77 | cv.valueType = Flags 78 | cv.valueFlags = make(map[string]bool) 79 | for _, f := range v.Options { 80 | 81 | if rand.Intn(2) == 0 { 82 | cv.valueFlags[f] = false 83 | } else { 84 | cv.valueFlags[f] = true 85 | } 86 | } 87 | return &cv 88 | } 89 | 90 | switch v.Type { 91 | case "bool": 92 | cv.valueType = Bool 93 | 94 | if rand.Intn(2) == 0 { 95 | cv.valueBool = false 96 | return &cv 97 | } else { 98 | cv.valueBool = true 99 | return &cv 100 | } 101 | case "int": 102 | cv.valueType = Int 103 | cv.valueInt = rand.Intn(intRange*2) - intRange 104 | return &cv 105 | case "unsigned": 106 | cv.valueType = Unsigned 107 | cv.valueUnsigned = uint32(rand.Intn(intRange)) 108 | return &cv 109 | default: 110 | if len(v.Options) != 0 { 111 | cv.valueType = String 112 | cv.valueString = v.Options[rand.Intn(len(v.Options))] 113 | return &cv 114 | } else { 115 | fmt.Println("Err: no type/:parameter for " + entry) 116 | return nil 117 | } 118 | } 119 | 120 | return nil 121 | } 122 | -------------------------------------------------------------------------------- /individual.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "io/ioutil" 5 | "math" 6 | "math/rand" 7 | "os" 8 | "os/exec" 9 | 10 | "github.com/sergi/go-diff/diffmatchpatch" 11 | ) 12 | 13 | type Individual struct { 14 | configValues []ConfigValue 15 | score uint32 16 | identifier uint32 17 | } 18 | 19 | func (ind Individual) String() (s string) { 20 | return ind.toOneLineConfig() 21 | } 22 | 23 | func (ind Individual) toOneLineConfig() (s string) { 24 | s = "{" 25 | 26 | for idx, cv := range ind.configValues { 27 | if idx != 0 { 28 | s += ", " 29 | } 30 | s += cv.String() 31 | } 32 | s += "}" 33 | return s 34 | } 35 | 36 | func (ind Individual) toClangFormatConfigFile() (s string) { 37 | s = "---\n" 38 | 39 | for _, cv := range ind.configValues { 40 | s += cv.String() + "\n" 41 | } 42 | s += "...\n" 43 | return s 44 | } 45 | 46 | func genIndividual(configEntries map[string]*ConfigEntry) (ind Individual) { 47 | 48 | ind.score = scoreUnitialized 49 | ind.identifier = uint32(rand.Intn(math.MaxUint32)) 50 | for entry := range configEntries { 51 | cv := generateConfigValue(entry, configEntries) 52 | if cv != nil { 53 | ind.configValues = append(ind.configValues, *cv) 54 | } 55 | } 56 | 57 | return ind 58 | } 59 | 60 | func (ind *Individual) UpdateScore(clangPath string, perfectSourceData []byte) error { 61 | ind.score = scoreUnitialized 62 | conf := ind.toClangFormatConfigFile() 63 | 64 | ioutil.WriteFile(".clang-format", []byte(conf), os.ModePerm) 65 | 66 | cmd := exec.Command(clangPath, "-style=file") 67 | stdin, err := cmd.StdinPipe() 68 | defer stdin.Close() 69 | if err != nil { 70 | return err 71 | } 72 | 73 | go func() { 74 | defer stdin.Close() 75 | stdin.Write(perfectSourceData) 76 | }() 77 | 78 | out, err := cmd.Output() 79 | 80 | dmp := diffmatchpatch.New() 81 | 82 | diffs := dmp.DiffMain(string(perfectSourceData), string(out), false) 83 | 84 | ind.score = uint32(dmp.DiffLevenshtein(diffs)) 85 | return nil 86 | } 87 | 88 | func (mother *Individual) mix(father *Individual, mutationRate uint32, configEntries map[string]*ConfigEntry) (baby Individual) { 89 | 90 | for i := 0; i < len(mother.configValues); i++ { 91 | var motherValue *ConfigValue 92 | var fatherValue *ConfigValue 93 | var babyValue ConfigValue 94 | motherValue = &mother.configValues[i] 95 | var j int 96 | for j = 0; j < len(father.configValues); j++ { 97 | if father.configValues[i].entry == motherValue.entry { 98 | fatherValue = &father.configValues[i] 99 | break 100 | } 101 | } 102 | if fatherValue == nil { 103 | baby.configValues = append(baby.configValues, *motherValue) 104 | continue 105 | } 106 | 107 | //Mix mother and father values 108 | switch motherValue.valueType { 109 | case String: 110 | fallthrough 111 | case Unsigned: 112 | fallthrough 113 | case Int: 114 | fallthrough 115 | case Bool: 116 | if rand.Intn(2) == 0 { 117 | babyValue = *motherValue 118 | } else { 119 | babyValue = *fatherValue 120 | } 121 | case Flags: 122 | babyValue.valueType = Flags 123 | babyValue.entry = motherValue.entry 124 | babyValue.valueFlags = make(map[string]bool) 125 | for f := range motherValue.valueFlags { 126 | if rand.Intn(2) == 0 { 127 | babyValue.valueFlags[f] = motherValue.valueFlags[f] 128 | } else { 129 | babyValue.valueFlags[f] = fatherValue.valueFlags[f] 130 | } 131 | } 132 | } 133 | 134 | //If mutation, override with new value 135 | if uint32(rand.Intn(100)) <= mutationRate { 136 | mutatedValue := generateConfigValue(motherValue.entry, configEntries) 137 | if mutatedValue != nil { 138 | babyValue = *mutatedValue 139 | } 140 | } 141 | baby.configValues = append(baby.configValues, babyValue) 142 | } 143 | 144 | baby.identifier = uint32(rand.Intn(math.MaxUint32)) 145 | baby.score = scoreUnitialized 146 | 147 | return 148 | } 149 | -------------------------------------------------------------------------------- /magic.go: -------------------------------------------------------------------------------- 1 | package main 2 | -------------------------------------------------------------------------------- /population.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "math" 6 | "math/rand" 7 | "sort" 8 | ) 9 | 10 | type Population struct { 11 | population []Individual 12 | generation uint32 13 | populationSize uint32 14 | } 15 | 16 | func genPopulation(populationSize uint32, configEntries map[string]*ConfigEntry) Population { 17 | var pop Population 18 | var i uint32 19 | for i = 0; i < populationSize; i++ { 20 | pop.population = append(pop.population, genIndividual(configEntries)) 21 | } 22 | pop.generation = 0 23 | pop.populationSize = populationSize 24 | //fmt.Println(pop) 25 | return pop 26 | } 27 | 28 | func (pop Population) String() string { 29 | var s string 30 | 31 | for idx, p := range pop.population { 32 | s += fmt.Sprintf("#%d: %s %d\n", idx, p.String(), p.score) 33 | } 34 | 35 | return s 36 | } 37 | 38 | func (pop Population) mix(mutationRate uint32, configEntries map[string]*ConfigEntry) { 39 | var i uint32 40 | for i = 0; i < pop.populationSize/2; i++ { 41 | var motherID int 42 | fatherID := rand.Intn(int(pop.populationSize / 2)) 43 | for { 44 | motherID := rand.Intn(int(pop.populationSize / 2)) 45 | if fatherID != motherID { 46 | break 47 | } 48 | } 49 | baby := pop.population[motherID].mix(&pop.population[fatherID], mutationRate, configEntries) 50 | baby.score = scoreUnitialized 51 | pop.population[i+pop.populationSize/2] = baby 52 | } 53 | } 54 | 55 | func (pop *Population) getStdDev(topN int) float64 { 56 | var avg float64 57 | var stdDev float64 58 | var count float64 59 | 60 | for idx, p := range pop.population { 61 | if idx > topN { 62 | break 63 | } 64 | //Ignore erroneous scores 65 | if p.score != scoreUnitialized { 66 | avg += float64(p.score) 67 | count += 1.0 68 | } 69 | } 70 | avg /= count 71 | 72 | stdDev = 0.0 73 | for idx, p := range pop.population { 74 | if idx > topN { 75 | break 76 | } 77 | //Ignore erroneous scores 78 | if p.score != scoreUnitialized { 79 | dev := math.Abs(float64(p.score) - avg) 80 | stdDev += dev * dev 81 | } 82 | } 83 | 84 | return math.Sqrt(stdDev / count) 85 | } 86 | 87 | func (pop *Population) sort() { 88 | sort.Sort(pop) 89 | } 90 | 91 | func (pop Population) Swap(i, j int) { 92 | pop.population[i], pop.population[j] = pop.population[j], pop.population[i] 93 | } 94 | 95 | func (pop Population) Less(i, j int) bool { 96 | return pop.population[i].score < pop.population[j].score 97 | } 98 | 99 | func (pop Population) Len() int { 100 | return int(pop.populationSize) 101 | } 102 | --------------------------------------------------------------------------------