Skip to content

Instantly share code, notes, and snippets.

@dev001hajipro
Created August 9, 2017 12:17
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save dev001hajipro/bacad84846d08b50baa3d1cab74ac753 to your computer and use it in GitHub Desktop.
Save dev001hajipro/bacad84846d08b50baa3d1cab74ac753 to your computer and use it in GitHub Desktop.
KotlinとJavaFXで遺伝的アルゴリズムで、特定の文字列に進化させる
import javafx.animation.AnimationTimer
import javafx.application.Application
import javafx.scene.Group
import javafx.scene.Scene
import javafx.scene.canvas.Canvas
import javafx.scene.canvas.GraphicsContext
import javafx.scene.paint.Color
import javafx.scene.text.Font
import javafx.stage.Stage
fun random(min: Double = 0.0, max: Double = 1.1) = (Math.random() * (max - min + 1)) + min
fun newChar(): Char {
var c = Math.floor(random(64.0, 122.0)).toInt()
c = if (c == 64) 32 else c // @ mark to space.
return c.toChar()
}
fun main(args: Array<String>) {
Application.launch(App::class.java, *args)
}
/**
* JavaFXのCanvasを用意して、AnimationTimerでゲームループを実行します。
* ProcessingやP5.jsと同じように、アプリ起動時に、start()関数が呼ばれ、
* ゲームループ内で、draw(ctx:GraphicsContext)が呼ばれます。
* start関数とdraw関数はユーザーが実装する必要があります。
*/
class App : Application() {
lateinit var canvas: Canvas
lateinit var ctx: GraphicsContext
override fun start(primaryStage: Stage) {
canvas = Canvas(1280.0, 720.0)
ctx = canvas.graphicsContext2D
val root = Group()
root.children.add(canvas)
object : AnimationTimer() {
override fun handle(now: Long) {
draw(ctx)
}
}.start()
with(primaryStage) {
title = "遺伝的アルゴリズムでランダム文字列を'$TARGET'に進化させる"
scene = Scene(root, canvas.width, canvas.height)
show()
}
}
}
//////////////////////////////////////////////
fun <T> randIndex(src: List<T>): Int = Math.floor(Math.random() * src.size).toInt()
class DNA(targetLength: Int = 10, var fitness: Double = 0.0) {
var genes: List<Char> = List(targetLength, { newChar() })
fun calcFitness(target: String) {
fitness = calcScore() / target.length
}
fun calcScore(): Double = genes.foldIndexed(0.0) { i, acc, c -> if (TARGET[i] == c) acc + 1.0 else acc }
fun crossover(partnerDNA: DNA): DNA {
val childDNA = DNA(genes.size)
val midpoint = Math.floor(Math.random() * genes.size)
childDNA.genes = genes.mapIndexed { i, c -> if (i > midpoint) c else partnerDNA.genes[i] }
return childDNA
}
fun mutate(rate: Double): DNA {
genes = genes.map { if (Math.random() < rate) newChar() else it }
return this
}
}
data class Point(val x: Int, val y: Int)
fun xy(x: Int): Point = Point(x % 4, x / 4)
const val POPULATION_COUNT = 150
const val MUTATION_RATE = 0.01
const val TARGET = "to be or not to be"
var foundFlag = false
// ステップ1. 集団の作成
var population: List<DNA> = List(POPULATION_COUNT) { DNA(TARGET.length) }
var generationCount = 0
fun draw(ctx: GraphicsContext) {
ctx.fill = Color(1.0, 1.0, 1.0, 1.0)
ctx.fillRect(0.0, 0.0, ctx.canvas.width, ctx.canvas.height)
if (!foundFlag) {
// ステップ2.選択
// 集団の各要素の適応度を計算
population.forEach { it.calcFitness(TARGET) }
// 交配プールを作成
val matingPool = population.map { dna ->
val n = Math.floor(dna.fitness * 100.0).toInt()
List(n) { dna }
}.flatten()
// ステップ3.生殖(reproduction)
population = population.map {
val dad = matingPool[randIndex(matingPool)]
val mam = matingPool[randIndex(matingPool)]
val child = dad.crossover(mam)
child.mutate(MUTATION_RATE)
}
population.forEach { it.calcFitness(TARGET) }
foundFlag = (population.indexOfFirst { dna -> dna.genes.joinToString("") == TARGET } != -1)
++generationCount
}
// 表示 /////
ctx.font = Font("Monospaced", 14.0)
population.map { it.genes.joinToString("") }
.forEachIndexed { i, s ->
val (x, y) = xy(i)
ctx.fill = if (s == TARGET) Color.RED else Color.BLACK
ctx.fillText(s, 10.0 + (x * 150), 30.0 + (y * 15))
}
ctx.fill = Color.BLACK
val maxDNA = population.maxBy { dna -> dna.fitness } ?: population[0]
ctx.font = Font("Monospaced", 32.0)
ctx.fillText("Best phrase : ${maxDNA.genes.joinToString("")}", 1280.0 / 2, 50.0)
ctx.font = Font("Monospaced", 20.0)
ctx.fillText("total generations: $generationCount", 1280.0 / 2, 50.0 + 20.0 * 1)
// average
//SUM_DNA_LENGTH
val averageFitness = population.map { it.calcScore() }.average()
ctx.fillText("average fitness : ${Math.floor(averageFitness * 100.0) / 100.0}", 1280.0 / 2, 50.0 + 20.0 * 2)
ctx.fillText("mutation rate : $MUTATION_RATE", 1280.0 / 2, 50.0 + 20.0 * 3)
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment