Go语言实现遗传算法的实例代码

在没介绍正文之前先给大家补充点go语言基本知识及实例。

Go 语言教程

Go 是一个开源的编程语言,它能让构造简单、可靠且高效的软件变得容易。

Go是从2007年末由Robert Griesemer, Rob Pike, Ken Thompson主持开发,后来还加入了Ian Lance Taylor, Russ Cox等人,并最终于2009年11月开源,在2012年早些时候发布了Go 1稳定版本。现在Go的开发已经是完全开放的,并且拥有一个活跃的社区。

Go 语言特色

简洁、快速、安全
并行、有趣、开源
内存管理、v数组安全、编译迅速

Go 语言用途

Go 语言被设计成一门应用于搭载 Web 服务器,存储集群或类似用途的巨型中央服务器的系统编程语言。

对于高性能分布式系统领域而言,Go 语言无疑比大多数其它语言有着更高的开发效率。它提供了海量并行的支持,这对于游戏服务端的开发而言是再好不过了。

第一个 Go 程序

接下来我们来编写第一个 Go 程序 hello.go(Go 语言源文件的扩展是 .go),代码如下:

实例

package main
import "fmt"
func main() {
  fmt.Println("Hello, World!")
}

执行以上代码输出

$ go run hello.go
Hello, World!

好了,正文开始。

出于好玩的心态,我决定学习一下Go语言。我认为学习新语言最好的方法就是深入学习,并且尽可能多犯错误。这样做虽然可能会很慢,但是可以确保在后面的过程中再也不会出现编译的错误。

Go语言与我习惯的其他语言不同。Go更喜欢自己单独实现,而其他像Java这类语言更喜欢继承。其实在Go语言里面根本没有继承这种概念,因为它压根就没有对象这一说法。比如说C语言,它有结构体,但是没有类。但是这样它还是可以有像“构造者”这样的常见思想和设计模式(一种在这种情况下有序地产生结构体的方式)。

Go语言坚决拥护组合(composition),同时也很反对继承的做法,在网络上引起了强烈的讨论,同时也让人们重新思考了语言该往哪个方向发展。所以,从这个角度来看,Go语言与其它语言的差别可能也没有那么大。

本文将重点介绍如何用Go语言实现遗传算法。如果你还没有参加过GoLang Tour,我还建议你快速看一下这门语言的介绍。

话不多说,让我们开始从代码说起吧!第一个例子与我以前做过的很类似:找到一个二次的最小值。

type GeneticAlgorithmSettings struct {
 PopulationSize int
 MutationRate int
 CrossoverRate int
 NumGenerations int
 KeepBestAcrossPopulation bool
}
type GeneticAlgorithmRunner interface {
 GenerateInitialPopulation(populationSize int) []interface{}
 PerformCrossover(individual1, individual2 interface{}, mutationRate int) interface{}
 PerformMutation(individual interface{}) interface{}
 Sort([]interface{})
}

我立马定义了一组设置,以便在稍后启动的算法中用到。

第二部分的GeneticAlgorithmRunner这个看起来有点奇怪。GeneticAlgorithmRunner是一个接口,询问如何生成初始种群,执行corssovers和mutataions,并对答案进行排序,以便在Population中保持最好的个体,这样下一代才会更加优秀。我认为这看起来很奇怪,因为“接口”通常用于面向对象的语言,通常会要求对象实现某些特性和方法。这里没有什么差别。这一小段代码实际上是在说,它正在请求一些东西来定义这些方法的细节。我是这样做的:

type QuadraticGA struct {}
func (l QuadraticGA) GenerateInitialPopulation(populationSize int) []interface{}{
 initialPopulation := make([]interface{}, 0, populationSize)
 for i:= 0; i < populationSize; i++ {
  initialPopulation = append(initialPopulation, makeNewEntry())
 }
 return initialPopulation
}
func (l QuadraticGA) PerformCrossover(result1, result2 interface{}, _ int) interface{}{
 return (result1.(float64) + result2.(float64)) / 2
}
func (l QuadraticGA) PerformMutation(_ interface{}, _ int) interface{}{
 return makeNewEntry()
}
func (l QuadraticGA) Sort(population []interface{}){
 sort.Slice(population, func(i, j int) bool {
  return calculate(population[i].(float64)) > calculate(population[j].(float64))
 })
}

更奇怪的是,我从来没有提到过这些方法的接口。请记住,因为没有对象,也没有继承。QuadraticGA结构体是一个空白对象,隐式地作为GeneticAlgorithmRunner。每个必需的方法都在括号中绑定到该结构体,就像Java中的“@ override”。现在,结构体和设置需要传递给运行该算法的模块。

settings := ga.GeneticAlgorithmSettings{
  PopulationSize: 5,
  MutationRate: 10,
  CrossoverRate: 100,
  NumGenerations: 20,
  KeepBestAcrossPopulation: true,
}
best, err := ga.Run(QuadraticGA{}, settings)
if err != nil {
  println(err)
}else{
  fmt.Printf("Best: x: %f y: %f\n", best, calculate(best.(float64)))
}

很简单,对吧?“QuadraticGA {}”只是简单地创建了该结构的一个新实例,其余的则由Run()方法完成。该方法返回搜索结果和发生的任何错误,因为Go不相信try / catch——另一场战争作者采取了严格的设计立场。

现在来计算每个项的性能,以求二次函数求出的二次函数来求出一个新的X值的方法:

func makeNewEntry() float64 {
  return highRange * rand.Float64()
}
func calculate(x float64) float64 {
  return math.Pow(x, 2) - 6*x + 2 // minimum should be at x=3
}

既然已经为二次实现创建了接口,那么GA本身需要完成:

func Run(geneticAlgoRunner GeneticAlgorithmRunner, settings GeneticAlgorithmSettings) (interface{}, error){
  population := geneticAlgoRunner.GenerateInitialPopulation(settings.PopulationSize)
  geneticAlgoRunner.Sort(population)
  bestSoFar := population[len(population) - 1]
  for i:= 0; i < settings.NumGenerations; i++ {
   newPopulation := make([]interface{}, 0, settings.PopulationSize)
   if settings.KeepBestAcrossPopulation {
     newPopulation = append(newPopulation, bestSoFar)
   }
   // perform crossovers with random selection
   probabilisticListOfPerformers := createStochasticProbableListOfIndividuals(population)
   newPopIndex := 0
   if settings.KeepBestAcrossPopulation{
     newPopIndex = 1
   }
   for ; newPopIndex < settings.PopulationSize; newPopIndex++ {
     indexSelection1 := rand.Int() % len(probabilisticListOfPerformers)
     indexSelection2 := rand.Int() % len(probabilisticListOfPerformers)
     // crossover
     newIndividual := geneticAlgoRunner.PerformCrossover(
      probabilisticListOfPerformers[indexSelection1],
      probabilisticListOfPerformers[indexSelection2], settings.CrossoverRate)
     // mutate
     if rand.Intn(101) < settings.MutationRate {
      newIndividual = geneticAlgoRunner.PerformMutation(newIndividual)
     }
     newPopulation = append(newPopulation, newIndividual)
   }
   population = newPopulation
   // sort by performance
   geneticAlgoRunner.Sort(population)
   // keep the best so far
   bestSoFar = population[len(population) - 1]
  }
  return bestSoFar, nil
}
func createStochasticProbableListOfIndividuals(population []interface{}) []interface{} {
  totalCount, populationLength:= 0, len(population)
  for j:= 0; j < populationLength; j++ {
   totalCount += j
  }
  probableIndividuals := make([]interface{}, 0, totalCount)
  for index, individual := range population {
   for i:= 0; i < index; i++{
     probableIndividuals = append(probableIndividuals, individual)
   }
  }
  return probableIndividuals
}

很像以前,一个新的人口被创造出来,人口的成员将会世代交配,而他们的后代可能携带突变。一个人的表现越好,就越有可能交配。随着时间的推移,算法收敛到最好的答案,或者至少是一个相当不错的答案。

那么当它运行时,它返回了什么呢?

Best: x: 3.072833 y: -6.994695

不坏!由于人口规模只有5、20代,而且输入的范围被限制在[0 100],这一搜索就钉在了顶点上。

现在,您可能想知道为什么我定义了所有的接口方法来返回“接口{}”。这就像Go和generics一样。没有对象,因此没有对象类型返回,但是没有描述的大小的数据仍然可以在堆栈上传递。这本质上也是这个返回类型的含义:它传递一些已知的和类似的类型的对象。有了这个“泛型”,我就可以将GA移动到它自己的包中,并将相同的代码移到多个不同类型的数据上。

我们有两个输入的3D二次方程,而不是一个二维二次方程的单个输入。接口方法只需要很小的改变:

type Quad3D struct {
  x, y float64
}
func makeNewQuadEntry(newX, newY float64) Quad3D {
  return Quad3D{
   x: newX,
   y: newY,
  }
}
func calculate3D(entry Quad3D) float64 {
  return math.Pow(entry.x, 2)- 6 * entry.x + math.Pow(entry.y, 2)- 6 * entry.y + 2
}
type Quadratic3dGA struct {
}
func (l Quadratic3dGA) GenerateInitialPopulation(populationSize int)[]interface{}{
  initialPopulation := make([]interface{}, 0, populationSize)
  for i:= 0; i < populationSize; i++ { initialPopulation = append(initialPopulation, makeNewQuadEntry(makeNewEntry(), makeNewEntry())) } return initialPopulation } func (l Quadratic3dGA) PerformCrossover(result1, result2 interface{}, mutationRate int) interface{}{ r1Entry, r2Entry := result1.(Quad3D), result2.(Quad3D) return makeNewQuadEntry((r1Entry.x + r2Entry.x) / 2, (r1Entry.y + r2Entry.y) / 2,) } func (l Quadratic3dGA) PerformMutation(_ interface{}) interface{}{ return makeNewQuadEntry(makeNewEntry(), makeNewEntry()) } func (l Quadratic3dGA) Sort(population []interface{}){ sort.Slice(population, func(i, j int) bool { return calculate3D(population[i].(Quad3D)) > calculate3D(population[j].(Quad3D))
  })
}
func quadratic3dMain(){
  settings := ga.GeneticAlgorithmSettings{
   PopulationSize: 25,
   MutationRate: 10,
   CrossoverRate: 100,
   NumGenerations: 20,
   KeepBestAcrossPopulation: true,
  }
  best, err := ga.Run(Quadratic3dGA{}, settings)
  entry := best.(Quad3D)
  if err != nil {
   println(err)
  }else{
   fmt.Printf("Best: x: %f y: %f z: %f\n", entry.x, entry.y, calculate3D(entry))
  }
}

而不是到处都是float64s,任何地方都可以通过Quad3D的条目;每一个都有一个X和一个Y值。对于创建的每个条目,都使用contructor makeNewQuadEntry创建。Run()方法中的代码都没有更改。

当它运行时,我们得到这个输出:

Best: x: 3.891671 y: 4.554884 z: -12.787259

很接近了!

哦,我忘了说走快了!在Java中执行此操作时,即使使用相同的设置,也会有明显的等待时间。在一个相对较小的范围内求解二次方程并不是很复杂,但它对一个人来说是值得注意的。

Go是本地编译的,比如C。当二进制执行时,它似乎马上就吐出一个答案。这里有一个简单的方法来度量每次运行的执行时间:

func main() {
  beforeQuadTime := time.Now()
  quadraticMain()
  afterQuadTime := time.Since(beforeQuadTime)
  fmt.Printf("%d\n", afterQuadTime)
  before3dQuadTime := time.Now()
  quadratic3dMain()
  after3dQuatTime := time.Since(before3dQuadTime)
  fmt.Printf("%d\n", after3dQuatTime)
}

边注:我能说我很高兴我们是一个开发者社区,让他们从过去的错误中走出来,并把综合的时间模块和包构建成一种语言吗?Java 8 +拥有它们,Python拥有它们,并拥有它们。这使我开心。

现在的输出:

Best: x: 3.072833 y: -6.994695
136,876
Best: x: 3.891671 y: 4.554884 z: -12.787259
4,142,778

那“近乎瞬间”的感觉是我想要传达的,现在我们有了很难的数字。136,876看起来很大,但要在纳秒内报告时间。

重申一遍:纳秒。不是几毫秒,我们都习惯了在互联网时代或者其他像Python和Java这样的通用语言;纳秒。1/1,000,000毫秒。

这意味着我们在不到一毫秒的时间里找到了一个使用遗传算法来搜索答案的二次方程的答案。这句话,“该死的瞬间”似乎很合适,不是吗?这包括打印到终端。

那么,要计算更密集的东西呢?在我展示一种寻找好的梦幻足球lineups的方法之前,我在Fanduel上使用。这包括从电子表格中读取数据,制作和过滤lineups,并进行更复杂的交叉和突变。强制寻找最佳解决方案可能需要超过75,000年(至少使用我当时使用的Python)。

我不需要再检查所有的细节,你可以自己去看代码,但我会在这里显示输出:

Best: 121.409960:, $58100
QB: Aaron Rodgers - 23.777778
RB: Latavius Murray - 15.228571
RB: DeMarco Murray - 19.980000
WR: Kelvin Benjamin - 11.800000
WR: Stefon Diggs - 14.312500
WR: Alshon Jeffery - 9.888889
TE: Connor Hamlett - 8.200000
D: Philadelphia Eagles - 10.777778
K: Phil Dawson - 7.444444
16,010,182

哦,是的!现在看来这是一个很好的阵容!它只需要16毫秒就能找到。

现在,这个遗传算法可以改进了。与C一样,当将对象传递给方法时,将在堆栈上复制对象(读取数据)。随着对象大小的增长,最好不要反复复制它们,而是要在堆中创建它们,并在周围传递指针。目前,我将把它作为未来的工作。

Go也被用coroutines和信道的原生支持编写,利用多个内核来解决一个问题,比过去简单多了,相比于单核时代的其他语言来说,这是一个巨大的优势。我想要增强这个算法来使用这些工具,但这也必须留给以后的工作。

我很享受学习的过程。对于我来说,用组合而不是继承来考虑工程解决方案是很困难的,因为我已经习惯了8年以上的时间,也是我学会编程的方式。但是每种语言和方式都有各自的优点和缺点;每一种语言在我的工具中都是不同的工具。对于任何担心尝试的人,不要。有一个驼峰(更像是一个减速带),但你很快就会克服它,走上成功之路。

还有一些我喜欢的东西,我喜欢其他语言,主要是一组基本的函数方法来操作数据。我需要一个lambda函数和方法来映射、减少和筛选数据的数组或部分。设计人员反对功能实现的理由是,代码应该总是简单、易于阅读和编写,并且这与for循环是可实现的。我认为,映射、过滤和减少通常更容易读和写,但这是一场已经在肆虐的战争中的争论。

尽管我与一些开发人员的观点存在分歧,以及我必须考虑解决问题的不同方式,但Go真的是一种很好的语言。我鼓励大家在学习一两门语言后再试一试。它很快就成为了最流行的语言之一,有很多原因可以解释为什么。我期待着在未来更多地使用它。

总结

以上所述是小编给大家介绍的Go语言实现遗传算法的实例代码,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对我们网站的支持!

(0)

相关推荐

  • Python和GO语言实现的消息摘要算法示例

    常用的消息摘要算法有MD5和SHA,这些算法在python和go的库中都有,需要时候调用下就OK了,这里总结下python和go的实现. 一.python消息摘要示例 代码如下: 复制代码 代码如下: #! /usr/bin/python '''       File      : testHash.py       Author    : Mike       E-Mail    : Mike_Zhang@live.com ''' import hashlib src = raw_input(

  • Go语言实现的树形结构数据比较算法实例

    本文实例讲述了Go语言实现的树形结构数据比较算法.分享给大家供大家参考.具体实现方法如下: 复制代码 代码如下: // Two binary trees may be of different shapes, // but have the same contents. For example: // //        4               6 //      2   6          4     7 //     1 3 5 7       2   5 //          

  • 深入解析快速排序算法的原理及其Go语言版实现

    快速排序是一种基于分治技术的重要排序算法.不像归并排序是按照元素在数组中的位置对它们进行划分,快速排序按照元素的值对它们进行划分.具体来说,它对给定数组中的元素进行重新排列,以得到一个快速排序的分区.在一个分区中,所有在s下标之前的元素都小于等于A[s],所有在s下标之后的元素都大于等于A[s]. 显然,建立了一个分区以后,A[s]已经位于它在有序数组中的最终位置,接下来我们可以继续对A[s]前和A[s]后的子数组分别进行排序(使用同样的方法). 为了排序一个数组A的全部元素,初始调用的是QUI

  • Go语言实现汉诺塔算法

    hano.go package main import ( "bufio" "fmt" "os" "strconv" ) func main() { fmt.Print("输入要移动的盘子数:") reader := bufio.NewReader(os.Stdin) lool: data, _, _ := reader.ReadLine() n, err := strconv.Atoi(string(da

  • go语言睡眠排序算法实例分析

    本文实例讲述了go语言睡眠排序算法.分享给大家供大家参考.具体分析如下: 睡眠排序算法是一个天才程序员发明的,想法很简单,就是针对数组里的不同的数开多个线程,每个线程根据数的大小睡眠,自然睡的时间越长的,数越大,哈哈,搞笑吧,这种算法看起来很荒唐,但实际上很天才,它可以充分利用多核cpu进行计算. 复制代码 代码如下: package main import (     "fmt"     "time" ) func main() {     tab := []in

  • GO语言利用K近邻算法实现小说鉴黄

    Usuage:   go run kNN.go --file="data.txt" 关键是向量点的选择和阈值的判定 样本数据来自国家新闻出版总署发布通知公布的<40部淫秽色情网络小说名单> package main import ( "bufio" "flag" "fmt" "io" "log" "math" "os" "pa

  • Go语言实现AzDG可逆加密算法实例

    本文实例讲述了Go语言实现AzDG可逆加密算法.分享给大家供大家参考.具体实现方法如下: 复制代码 代码如下: package main import (  "fmt"  "crypto/md5"  "encoding/base64"  "time" ) var cipher = "密鑰" var h = md5.New() func cipherEncode(sourceText string) stri

  • Go语言展现快速排序算法全过程的思路及代码示例

    快速排序算法 快速排序是一个递归的思想,首先选择一个数作为基数,把数组中小于它的数放在它的左边,把大于它的数放在它的右边,然后对左右两边的数递归进行排序. 算法的关键部分是实现数组的划分,即怎么把数组的元素划分成两部分,使得左边的数比基数小,右边的数比基数大.划分有许多不同的实现方法,这里主要使用单向扫描的方法,后面再稍微介绍双向扫描的方法. 选择最右边的数字作为基数.使用一个变量j记录当前左边数字(比基数小的数)的最右的下标值.然后使用变量i从左到右遍历数组,如果a[i]比基数小,说明a[i]

  • Go语言通过Luhn算法验证信用卡卡号是否有效的方法

    本文实例讲述了Go语言通过Luhn算法验证信用卡卡号是否有效的方法.分享给大家供大家参考.具体实现方法如下: 复制代码 代码如下: package main import (     "fmt"     "strings" ) const input = `49927398716 49927398717 1234567812345678 1234567812345670` var t = [...]int{0, 2, 4, 6, 8, 1, 3, 5, 7, 9}

  • Go语言实现遗传算法的实例代码

    在没介绍正文之前先给大家补充点go语言基本知识及实例. Go 语言教程 Go 是一个开源的编程语言,它能让构造简单.可靠且高效的软件变得容易. Go是从2007年末由Robert Griesemer, Rob Pike, Ken Thompson主持开发,后来还加入了Ian Lance Taylor, Russ Cox等人,并最终于2009年11月开源,在2012年早些时候发布了Go 1稳定版本.现在Go的开发已经是完全开放的,并且拥有一个活跃的社区. Go 语言特色 简洁.快速.安全 并行.有

  • C语言动态顺序表实例代码

    目录 顺序表概念: 一.准备工作 二.顺序表的基本操作  1.顺序表的初始化函数 2.尾插函数(在尾部插入数据) 3.头插函数(在数组头部插入数据)  4.尾删函数 5.头删函数 6.在第pos的位置插入数据 7.删除第pos个位置的数据 8.修改第pos个位置的数据 9.查找函数. 10.销毁函数 11.打印函数 三.总代码: 顺序表概念:         顺序表是用一段物理地址连续的存储单元依次存储数据元素的线性结构.一般情况下用数组存储.在数组上完成数据的增删查改. 代码解析: 一.准备工

  • C语言打印各种图案实例代码

    1.线段图案 #include<stdio.h> int main() { int a = 0; while(~scanf("%d",&a)) { int i = 0; for(i=0;i<a;i++) { printf("*"); } printf("\n"); } return 0; } 2.正方形图案 #include<stdio.h> int main() { int a = 0; while(~sc

  • C语言实现三子棋实例代码

    我是半自主的完成了这个程序,看了B站鹏哥视频并仔细思索后才做出来的,我没有完全采用他的方法,导致程序还有一些不足之处,还请各位大佬指出. 首先,我将该程序的实现分为3个板块,main,game.h,和game.c; 代码如下: #define _CRT_SECURE_NO_WARNINGS #include"game.h" void menu() { printf("****************************************\n"); prin

  • C语言音乐播放器实例代码

    实例代码如下: #include <stdio.h> #include<dirent.h> #include<stdlib.h> #include<unistd.h> #include<string.h> typedef struct node_ node_t; struct node_{ char* name;//gequming node_t * prev; node_t * next; }; node_t *head = NULL; int

  • C 语言插入排序算法及实例代码

    插入排序是排序算法的一种,它不改变原有的序列(数组),而是创建一个新的序列,在新序列上进行操作. 这里以从小到大排序为例进行讲解. 基本思想及举例说明 插入排序的基本思想是,将元素逐个添加到已经排序好的数组中去,同时要求,插入的元素必须在正确的位置,这样原来排序好的数组是仍然有序的. 在实际使用中,通常是排序整个无序数组,所以把这个无序数组分为两部分排序好的子数组和待插入的元素.第一轮时,将第一个元素作为排序好的子数组,插入第二个元素:第二轮,将前两个元素作为排序好的数组,插入第三个元素.以此类

  • C语言合并排序及实例代码

    归并排序也称合并排序,其算法思想是将待排序序列分为两部分,依次对分得的两个部分再次使用归并排序,之后再对其进行合并.仅从算法思想上了解归并排序会觉得很抽象,接下来就以对序列A[0], A[l]-, A[n-1]进行升序排列来进行讲解,在此采用自顶向下的实现方法. 操作步骤如下: (1)将所要进行的排序序列分为左右两个部分,如果要进行排序的序列的起始元素下标为first,最后一个元素的下标为last,那么左右两部分之间的临界点下标mid=(first+last)/2,这两部分分别是A[first

  • C语言朴素模式匹配算法实例代码

    一.什么是字符串的模式匹配? 字符串模式匹配:在主串中找到与模式串相同的子串,并返回其所在位置. 注意: ①.子串--主串的一部分,一定存在. ②.模式串--不一定能在主串中找到 二.朴素模式匹配算法 主串长度为n,模式串长度为m. 朴素模式匹配算法:将主串中所有长度为m的子串依次与模式串匹配对比,直到找到一个完全匹配的子串,或所有的子串都不匹配为止. 最多对比n-m+1个子串 (一)通过数组下标实现朴素模式匹配算法 若当前⼦串匹配失败,则主串指针 i 指向下⼀个⼦串的第⼀个位置,模式串指针 j

  • C语言实现分治法实例

    本文为大家分享了C语言实现分治法实例代码,供大家参考,具体内容如下 使用分治法求最大值 这个函数将数组a[l]...a[r]分成a[l],...,a[m]和a[m+1],...a[r]两部分,分别求出每一部分的最大元素(递归地),并返回较大的那一个作为整个数组的最大元素.如果数组大小是偶数,则两部分大小相等;如果是奇数,第一部分比第二部分的大小大1. #include <cstdio> #include <cstdlib> #include <algorithm> #i

  • C语言中数据结构之链表归并排序实例代码

    C语言中数据结构之链表归并排序实例代码 问题 设有两个无头结点的单链表,头指针分别为ha,hb,链中有数据域data,链域next,两链表的数据都按递增排序存放,现要求将hb表归到ha表中,且归并后ha仍递增序,归并中ha表中已有的数据若hb中也有,则hb中的数据不归并到ha中,hb的链表在算法中不允许破坏. 源程序 #include <stdio.h> #include<stdlib.h> #define N1 6 /*链表La的长度*/ #define N2 6 /*链表Lb的

随机推荐