Advertisement
Guest User

Untitled

a guest
Nov 21st, 2019
100
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Kotlin 13.78 KB | None | 0 0
  1. import java.time.LocalDateTime
  2. import java.util.*
  3. import kotlin.math.abs
  4. import kotlin.random.Random
  5.  
  6. typealias Tile = Pair<Int, Int>
  7. typealias Board = Array<IntArray>
  8. typealias Path = List<Board>
  9.  
  10. typealias Genome = List<Pair<Board, Tile>>
  11.  
  12. class Puzzle {
  13.     companion object {
  14.         const val MATRIX_SIZE = 3
  15.         val START_POINT = Tile(0, 1)
  16.         val END_POINT = Tile(0, 0)
  17.         private const val MIN_STATES = 31
  18.         const val POPULATION_SIZE = 300
  19.     }
  20.  
  21.     private inner class TableNode(
  22.         val moves: Int,
  23.         val path: Path,
  24.         val currentState: Board,
  25.         val blankTile: Tile
  26.     ) : Comparable<TableNode> {
  27.         val f: Int
  28.             get() = moves + currentState.manhattanDistance()
  29.  
  30.         override fun compareTo(other: TableNode): Int {
  31.             return this.f.compareTo(other.f)
  32.         }
  33.     }
  34.  
  35.     private val startMatrix: Board = arrayOf(
  36.         intArrayOf(8, 0, 6),
  37.         intArrayOf(5, 4, 7),
  38.         intArrayOf(2, 3, 1)
  39.     )
  40.  
  41.     private val goalMatrix: Board = arrayOf(
  42.         intArrayOf(0, 1, 2),
  43.         intArrayOf(3, 4, 5),
  44.         intArrayOf(6, 7, 8)
  45.     )
  46.  
  47.     private fun Board.manhattanDistance(): Int {
  48.         var manhattanDistanceSum = 0
  49.         for (row in 0 until MATRIX_SIZE)
  50.             for (col in 0 until MATRIX_SIZE) {
  51.                 val value = this[row][col]
  52.                 if (value != 0) {
  53.                     val targetX: Int = value / MATRIX_SIZE
  54.                     val targetY: Int = value % MATRIX_SIZE
  55.                     val dx = row - targetX
  56.                     val dy = col - targetY
  57.                     manhattanDistanceSum += abs(dx) + abs(dy)
  58.                 }
  59.             }
  60.         return manhattanDistanceSum
  61.     }
  62.  
  63.     private fun Board.copy() = Array(size) { get(it).clone() }
  64.  
  65.     private fun Board.newState(blankSpace: Tile, newTile: Tile): Board {
  66.         val newState = this.copy()
  67.         newState[blankSpace.first][blankSpace.second] = newState[newTile.first][newTile.second].also {
  68.             newState[newTile.first][newTile.second] = newState[blankSpace.first][blankSpace.second]
  69.         }
  70.         return newState
  71.     }
  72.  
  73.     private fun Board.misplacedTiles(): Int {
  74.         var result = 0
  75.         for (row in 0 until MATRIX_SIZE)
  76.             for (col in 0 until MATRIX_SIZE)
  77.                 if (this[row][col] != goalMatrix[row][col])
  78.                     result++
  79.         return result - 1
  80.     }
  81.  
  82.     private fun Tile.isInBounds(): Boolean {
  83.         return first in 0 until MATRIX_SIZE &&
  84.                 second in 0 until MATRIX_SIZE
  85.     }
  86.  
  87.     private val usedTables = mutableListOf<Board>()
  88.     private fun MutableList<Board>.isNewState(element: Board): Boolean {
  89.         return !this.map { it.contentDeepEquals(element) }.contains(true)
  90.     }
  91.  
  92.     private fun PriorityQueue<TableNode>.addAndMarkAsPassed(node: TableNode) {
  93.         usedTables.add(node.currentState)
  94.         add(node)
  95.     }
  96.  
  97.     fun solveWithAStar(): Pair<Int, Path> {
  98.         usedTables.clear()
  99.         val priorityQueue = PriorityQueue<TableNode>()
  100.         priorityQueue.addAndMarkAsPassed(TableNode(0, listOf<Board>(startMatrix), startMatrix, START_POINT))
  101.         while (priorityQueue.isNotEmpty()) {
  102.             val currentNode = priorityQueue.peek()
  103.             if (currentNode.currentState.manhattanDistance() == 0)
  104.                 return Pair(currentNode.moves, currentNode.path)
  105.             priorityQueue.remove()
  106.             Direction.values().forEach { dir ->
  107.                 val newTile = dir.nextTile(currentNode.blankTile)
  108.                 if (newTile.isInBounds()) {
  109.                     val newBoard = currentNode.currentState.newState(currentNode.blankTile, newTile)
  110.                     if (usedTables.isNewState(newBoard)) {
  111.                         val path = currentNode.path.toMutableList()
  112.                         path.add(newBoard)
  113.                         priorityQueue.addAndMarkAsPassed(TableNode((currentNode.moves + 1), path, newBoard, newTile))
  114.                     }
  115.                 }
  116.             }
  117.         }
  118.         return Pair(0, mutableListOf())
  119.     }
  120.  
  121.     fun solveWithBeamSearch(beamWidth: Int): Pair<Int, Path> {
  122.         usedTables.clear()
  123.         val priorityQueue = PriorityQueue<TableNode>()
  124.         priorityQueue.addAndMarkAsPassed(TableNode(0, listOf<Board>(startMatrix), startMatrix, START_POINT))
  125.         while (priorityQueue.isNotEmpty()) {
  126.             val currentNode = priorityQueue.peek()
  127.             if (currentNode.currentState.manhattanDistance() == 0)
  128.                 return Pair(currentNode.moves, currentNode.path)
  129.             priorityQueue.remove()
  130.             val options = PriorityQueue<TableNode>()
  131.             Direction.values().forEach { dir ->
  132.                 val newTile = dir.nextTile(currentNode.blankTile)
  133.                 if (newTile.isInBounds()) {
  134.                     val newBoard = currentNode.currentState.newState(currentNode.blankTile, newTile)
  135.                     if (usedTables.isNewState(newBoard)) {
  136.                         val path = currentNode.path.toMutableList()
  137.                         path.add(newBoard)
  138.                         options.addAndMarkAsPassed(TableNode((currentNode.moves + 1), path, newBoard, newTile))
  139.                     }
  140.                 }
  141.             }
  142.             options.addAll(priorityQueue)
  143.             priorityQueue.clear()
  144.             repeat(beamWidth) {
  145.                 if (options.isEmpty()) {
  146.                     return@repeat
  147.                 }
  148.                 priorityQueue.add(options.remove())
  149.             }
  150.         }
  151.         return Pair(0, mutableListOf())
  152.     }
  153.  
  154.  
  155.     private fun Genome.fitness(): Int {
  156.         return this.mapIndexed { index, element -> element.first.manhattanDistance() + index }.sum()
  157.     }
  158.  
  159.     private fun getSplitIndexes(father: Genome, mother: Genome): Pair<Int, Int>? {
  160.         for (index in 1 until father.size)
  161.             for (mIndex in 1 until mother.size) {
  162.                 if (father[index].first.contentDeepEquals(mother[mIndex].first))
  163.                     return Pair(index, mIndex)
  164.             }
  165.         return null
  166.     }
  167.  
  168.     private fun crossOver(father: Genome, mother: Genome): List<Genome> {
  169.         val splitIndex = getSplitIndexes(father, mother)
  170.         splitIndex?.let {
  171.             val fatherFirstHalf = father.slice(0..splitIndex.first)
  172.             val fatherSecondHalf = father.drop(splitIndex.first + 1)
  173.             val motherFirstHalf = mother.slice(0..splitIndex.second)
  174.             val motherSecondHalf = mother.drop(splitIndex.second + 1)
  175.             return listOf<Genome>(
  176.                 fatherFirstHalf.plus(motherSecondHalf),
  177.                 motherFirstHalf.plus(fatherSecondHalf)
  178.             )
  179.         }
  180.         return mutableListOf()
  181.     }
  182.  
  183.     private fun Genome.maleMutate(): Genome {
  184.         if (4 == Random.nextInt(0, 100)) {  // 1%
  185.             val newTile = randomDirection(this[lastIndex - 1].second)
  186.             if (newTile.isInBounds()) {
  187.                 val newBoard = last().first.newState(last().second, newTile)
  188.                 this.mapIndexed { i, _ -> if (i == lastIndex) (Pair(newBoard, newTile)) }
  189.                 return this
  190.             }
  191.         }
  192.         return this
  193.     }
  194.  
  195.     private fun Genome.femaleMutate(): Genome {
  196.         if (4 == Random.nextInt(0, 100)) {  // 1%
  197.             val newTile = randomDirection(this[1].second)
  198.             if (newTile.isInBounds()) {
  199.                 val newBoard = first().first.newState(first().second, newTile)
  200.                 this.mapIndexed { i, _ -> if (i == 0) (Pair(newBoard, newTile)) }
  201.                 return this
  202.             }
  203.         }
  204.         return this
  205.     }
  206.  
  207.     private fun generateMalePopulation(): MutableList<Genome> {
  208.         val population = mutableListOf<Genome>()
  209.         repeat(POPULATION_SIZE / 2) {
  210.             population.add(generateGenomeFromBeginning())
  211.         }
  212.         return population
  213.     }
  214.  
  215.     private fun generateFemalePopulation(): MutableList<Genome> {
  216.         val population = mutableListOf<Genome>()
  217.         repeat(POPULATION_SIZE / 2) {
  218.             population.add(generateGenomeFromEnd())
  219.         }
  220.         return population
  221.     }
  222.  
  223.  
  224.     private fun randomDirection(point: Tile): Tile {
  225.         val choices = mutableListOf<Tile>()
  226.         Direction.values().forEach {
  227.             val newTile: Tile = it.nextTile(point)
  228.             if (newTile.isInBounds())
  229.                 choices.add(newTile)
  230.         }
  231.         return choices[Random.nextInt(choices.size)]
  232.     }
  233.  
  234.     private fun generateGenomeFromBeginning(): Genome {
  235.         val possibleSolution = mutableListOf(Pair(startMatrix, START_POINT))
  236.         val usedPaths = mutableListOf<Board>(startMatrix)
  237.         var loop = 0
  238.         while (possibleSolution.size < MIN_STATES) {
  239.             var flag = true
  240.             val currentNode = possibleSolution.last()
  241.             while (flag) {
  242.                 val newTile = randomDirection(currentNode.second)
  243.                 val newBoard = currentNode.first.newState(currentNode.second, newTile)
  244.                 if (usedPaths.isNewState(newBoard)) {
  245.                     usedPaths.add(newBoard)
  246.                     possibleSolution.add(Pair(newBoard, newTile))
  247.                     loop = 0
  248.                     flag = false
  249.                 }
  250.                 if (loop > Direction.values().size)
  251.                     return generateGenomeFromBeginning()
  252.                 loop++
  253.             }
  254.         }
  255.         return possibleSolution
  256.     }
  257.  
  258.     private fun generateGenomeFromEnd(): Genome {
  259.         val possibleSolution = mutableListOf(Pair(goalMatrix, END_POINT))
  260.         val usedPaths = mutableListOf(goalMatrix)
  261.         var loop = 0
  262.         while (possibleSolution.size < MIN_STATES) {
  263.             var flag = true
  264.             val currentNode = possibleSolution.first()
  265.             while (flag) {
  266.                 val newTile = randomDirection(currentNode.second)
  267.                 val newBoard = currentNode.first.newState(currentNode.second, newTile)
  268.                 if (usedPaths.isNewState(newBoard)) {
  269.                     usedPaths.add(newBoard)
  270.                     possibleSolution.add(0, Pair(newBoard, newTile))
  271.                     loop = 0
  272.                     flag = false
  273.                 }
  274.                 if (loop > Direction.values().size)
  275.                     return generateGenomeFromEnd()
  276.                 loop++
  277.             }
  278.         }
  279.         return possibleSolution
  280.     }
  281.  
  282.     private fun selection(male: Genome, females: List<Genome>): List<Int> {
  283.         val connections = mutableListOf<Int>()
  284.         females.forEachIndexed { index, female ->
  285.             if (getSplitIndexes(male, female) != null)
  286.                 connections.add(index)
  287.         }
  288.         return connections
  289.     }
  290.  
  291.     fun solveWithGeneticAlgorithm(): Genome {
  292.         var noBetter = 0
  293.         val malePopulation = generateMalePopulation()
  294.         val solutions: MutableList<Genome> = mutableListOf()
  295.         val femalePopulation = generateFemalePopulation()
  296.         var max: Genome = malePopulation.last()
  297.         var previousMax: Genome
  298.         val newMalePopulation = mutableListOf<Genome>()
  299.         val newFemalePopulation = mutableListOf<Genome>()
  300.  
  301.         while (!(malePopulation.last().last().first.contentDeepEquals(goalMatrix) && malePopulation.last().size == MIN_STATES)) {
  302.             if (noBetter > 10)
  303.                 return malePopulation.find { genome -> genome.last().first.contentDeepEquals(goalMatrix) }
  304.                     ?: solutions.minBy { it.size } ?: max
  305.             else {
  306.                 newMalePopulation.clear()
  307.                 newFemalePopulation.clear()
  308.                 for (index in 0 until malePopulation.size) {
  309.                     val connections = selection(malePopulation[index], femalePopulation)
  310.                     connections.forEach {
  311.                         val children = crossOver(malePopulation[index], femalePopulation[it])
  312.                         newMalePopulation.add(children[0])
  313.                         newFemalePopulation.add(children[1])
  314.                         solutions.add(children[0])
  315.                     }
  316.                 }
  317.                 malePopulation.asSequence().drop(newMalePopulation.size).plus(newMalePopulation)
  318.                     .sortedBy { it.fitness() }
  319.                     .map { it.maleMutate() }.toMutableList()
  320.                 femalePopulation.asSequence().drop(newFemalePopulation.size).plus(newFemalePopulation)
  321.                     .sortedBy { it.fitness() }
  322.                     .map { it.femaleMutate() }.toMutableList()
  323.                 previousMax = max
  324.                 solutions.maxBy { it.size }?.let { max = it }
  325.                 if (previousMax == max)
  326.                     noBetter++
  327.             }
  328.         }
  329.         return max
  330.     }
  331.  
  332.     fun printSolution(solution: Pair<Int, Path>) {
  333.         println("Number of steps: ${solution.first}")
  334.         solution.second.forEach {
  335.             println(Arrays.deepToString(it).replace("],", "],\n"))
  336.             println("-----------")
  337. //            Thread.sleep(2_000)
  338.         }
  339.     }
  340.  
  341.     enum class Direction {
  342.         LEFT, RIGHT, UP, DOWN;
  343.  
  344.         fun nextTile(point: Tile): Tile {
  345.             return when (this) {
  346.                 LEFT -> Tile(point.first, point.second - 1)
  347.                 RIGHT -> Tile(point.first, point.second + 1)
  348.                 UP -> Tile(point.first - 1, point.second)
  349.                 DOWN -> Tile(point.first + 1, point.second)
  350.             }
  351.         }
  352.     }
  353. }
  354.  
  355. fun main() {
  356.     val puzzle = Puzzle()
  357.  
  358. //    println("With A* search:")
  359. //    puzzle.printSolution(puzzle.solveWithAStar())
  360.  
  361. //    println("With Beam search:")
  362. //    puzzle.printSolution(puzzle.solveWithBeamSearch(3))
  363.  
  364.     println("With GA:")
  365.     val solution = puzzle.solveWithGeneticAlgorithm()
  366.     puzzle.printSolution(Pair(solution.size - 1, solution.map { it.first }))
  367. }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement