出于好玩,我决定学习Go语言。我认为学习一门新语言的最好方法是深入学习并尽可能多地犯错误。这样做虽然可能会比较慢,但是可以保证后面的过程不会出现编译错误。Go语言与我习惯的其他语言不同。Go更喜欢自己的实现,而其他语言如Java更喜欢继承。其实在Go语言中并没有继承这个概念,因为它根本就没有对象这个词。例如,C语言有结构,但没有类。但是这样它仍然可以有共同的想法和设计模式,比如“构造函数”(在这种情况下是一种顺序生成结构的方法)。Go语言坚决支持组合,同时也反对继承,在网上引起了激烈的讨论,同时也让人们重新思考语言应该往哪个方向发展。所以,从这一点来看,Go语言和其他语言的区别可能并没有那么大。本文将重点介绍如何用Go语言实现遗传算法。如果您还没有参加GoLang之旅,我还建议您快速浏览一下该语言的介绍。话不多说,下面开始上代码!第一个示例与我之前所做的非常相似:找到二次最小值。typeGeneticAlgorithmSettingsstruct{PopulationSizeintMutationRateintCrossoverRateintNumGenerationsintKeepBestAcrossPopulationbool}typeGeneticAlgorithmRunnerinterface{GenerateInitialPopulation(populationSizeint)[]interface{}PerformCrossover(individual1,individual2interface{},mutationRateint)interface{}PerformMutation(individualinterface{})interface{}Sort([]interface{})}我立马定义了稍后启动的算法中要使用的一组设置。第二部分的GeneticAlgorithmRunner看起来有点奇怪。GeneticAlgorithmRunner是一个接口,它询问如何生成初始种群,执行交叉和变异,并对答案进行排序以保留种群中最好的个体,以便下一代更好。我认为这似乎很奇怪,因为“接口”经常用于面向对象的语言中,并且通常需要对象来实现某些属性和方法。这里没有区别。这段代码实际上是在要求一些东西来定义这些方法的细节。我这样做:typeQuadraticGAstruct{}func(lQuadraticGA)GenerateInitialPopulation(populationSizeint)[]interface{}{initialPopulation:=make([]interface{},0,populationSize)fori:=0;icalculate(population[j].(float64))})}更奇怪的是我从来没有提到这些方法的接口。请记住,因为没有对象,所以没有继承。QuadraticGA结构是一个空对象,隐含地充当GeneticAlgorithmRunner。每个必需的方法都在括号中绑定到此结构,如Java中的“@override”。现在,需要将结构和设置传递给运行算法的模块。设置:=ga.GeneticAlgorithmSettings{PopulationSize:5,MutationRate:10,CrossoverRate:100,NumGenerations:20,KeepBestAcrossPopulation:true,}best,err:=ga.Run(QuadraticGA{},settings)iferr!=nil{println(err)}else{fmt.Printf("Best:x:%fy:%f\n",best,calculate(best.(float64)))}很简单吧?“QuadraticGA{}”只是创建一个结构的新实例,剩下的由Run()方法完成。此方法返回搜索结果和发生的任何错误,因为Go不相信try/catch-作者采取严格设计立场的另一场战争。现在让我们计算每个项目的性能,并使用二次函数找到一个新的X值:funcmakeNewEntry()float64{returnhighRange*rand.Float64()}funccalculate(xfloat64)float64{returnmath.Pow(x,2)-6*x+2//minimumshouldbeatx=3}现在已经为二次实现创建了接口,GA本身需要完成:(settings.PopulationSize)geneticAlgoRunner.Sort(population)bestSoFar:=population[len(population)-1]fori:=0;icalculate3D(population[j].(Quad3D))})}funcquadratic3dMain(){settings:=ga.GeneticAlgorithmSettings{PopulationSize:25,MutationRate:10,CrossoverRate:100,NumGenerations:20,KeepBestAcrossPopulation:true,}best,err:=ga.Run(Quadratic3dGA{},settings)entry:=best.(Quad3D)iferr!=nil{println(err)}else{fmt.Printf("最佳:x:%fy:%fz:%f\n",entry.x,entry.y,calculate3D(entry))}}而不是到处都是float64s,通过Quad3D的条目的任何地方;每个创建的每个条目都有一个X和一个Y值,使用构造函数makeNewQuadEntry创建。Run()方法中的代码均未更改。当它运行时,我们得到以下输出:Best:x:3.891671y:4.554884z:-12.787259这非常接近!哦,我忘了说快点!在Java中执行此操作时,即使使用相同的设置,也会有明显的等待时间。在相对较小的范围内求解二次方程并不是很复杂,但对人类来说是显而易见的。Go和C一样是原生编译的。当二进制文件执行时,它似乎马上就会吐出一个答案。这里有一个简单的方法来测量每次运行的执行时间::=time.Now()quadratic3dMain()after3dQuatTime:=time.Since(before3dQuadTime)fmt.Printf("%d\n",after3dQuatTime)}旁注:我能说什么我很高兴我们是一个开发者社区,让他们从过去的错误中走出来,将全面的时间模块和包构建成一种语言?Java8+有它们,Python有它们,也有它们。这让我开心。现在输出:Best:x:3.072833y:-6.994695136,876Best:x:3.891671y:4.554884z:-12.7872594,142,778这种“近乎瞬时”的感觉正是我试图传达的,现在我们有了硬数据。136,876看起来很大,但以纳秒为单位报告时间。重申一下:纳秒。不是我们在互联网时代或其他通用语言(如Python和Java)中都习惯的毫秒;纳秒。1/1,000,000毫秒。这意味着我们在不到一毫秒的时间内使用遗传算法搜索答案找到了二次方程的答案。“该死的瞬间”这句话似乎很合适,不是吗?这包括打印到终端。那么,对于计算量更大的东西?在我展示寻找优秀梦幻足球阵容的方法之前,我在Fanduel上使用了它。这包括从电子表格中读取数据、制作和过滤阵容,以及进行更复杂的交叉和突变。被迫找到一个***解决方案可能需要超过75,000年(至少对于我当时使用的Python而言)。我不需要再次检查所有细节,您可以自己查看代码,但我将在此处显示输出:Best:121.409960:,$58100WR:StefonDiggs-14.312500WR:AlshonJeffery-9.888889TE:ConnorHamlett-8.200000D:PhiladelphiaEagles-10.777778K:PhilDawson-7.44444416,010,182哦耶!现在看来这是一个不错的阵容!只需16ms即可启动。现在,可以改进这种遗传算法。与在C中一样,当一个对象被传递给一个方法时,该对象被复制(读取数据)到堆栈上。随着对象大小的增长,最好不要一遍又一遍地复制它们,而是在堆上创建它们,并传递指针。现在,我将把它留作未来的工作。Go还编写了对协程和通道的原生支持,这使得利用多核解决问题比以往任何时候都更容易,这是与单核时代的其他语言相比的巨大优势。我想增强此算法以使用这些工具,但这也必须留给未来的工作。我真的很享受学习的过程。我很难想到用组合代替继承的工程解决方案,因为我已经习惯了8年多,这就是我学习编程的方式。但是每种语言和方法都有自己的优点和缺点。每种语言都是我工具包中的不同工具。对于任何担心尝试的人,不要。有一个驼峰(更像是一个减速带),但您很快就会克服它并走上成功之路。还有一些我喜欢的东西,我喜欢其他语言,主要是一组基本的操作数据的功能方法。我需要一个lambda函数和方法来映射、减少和过滤数组或部分数据。设计者反对函数式实现的论点是代码应该总是简单的、易于阅读和编写的,而这可以通过for循环实现。在我看来,map、filter和reduce通常更易于阅读和编写,但这是一场已经激烈的战争中的争论。尽管我与一些开发人员意见不合,而且我必须以不同的方式思考解决问题,但Go确实是一门很好的语言。我鼓励大家在学习一两种语言后尝试一下。它很快成为最流行的语言之一,原因有很多。我期待将来更多地使用它。