This commit is contained in:
2025-06-22 19:10:36 +02:00
parent 54c7d351a7
commit d56a5b5ab7
2 changed files with 945 additions and 93 deletions

View File

@@ -1,8 +1,9 @@
{
"cells": [
{
"metadata": {},
"cell_type": "markdown",
"id": "3cf53c1e",
"metadata": {},
"source": [
"# 8 Queen Problem\n",
"## Install Requirements\n",
@@ -13,79 +14,751 @@
"python -m venv .env # linux\n",
"```\n",
"## Imports"
],
"id": "3cf53c1e"
]
},
{
"cell_type": "code",
"id": "2da4ea5d",
"metadata": {
"ExecuteTime": {
"end_time": "2025-06-17T16:03:48.531191Z",
"start_time": "2025-06-17T16:03:48.529197Z"
}
},
"execution_count": 22,
"id": "39ee826a",
"metadata": {},
"outputs": [],
"source": [
"import random"
],
"outputs": [],
"execution_count": 1
]
},
{
"cell_type": "markdown",
"id": "f6decafe",
"metadata": {},
"source": [
"## Field"
]
},
{
"cell_type": "code",
"id": "712bccdf",
"metadata": {
"ExecuteTime": {
"end_time": "2025-06-17T16:03:48.587628Z",
"start_time": "2025-06-17T16:03:48.585433Z"
}
},
"execution_count": 23,
"id": "33790f0d",
"metadata": {},
"outputs": [],
"source": [
"class Field:\n",
" def __init__(self, init_state = []):\n",
" self.state = []\n",
" \"\"\"\n",
" Represents the 8 Queens problem state and provides methods for state manipulation. Encodes initial state, actions, transition model, and heuristic functions.\n",
" \"\"\"\n",
"\n",
" if init_state is []:\n",
" for i in range(8):\n",
" self.state.append(random.randint(1,8))\n",
" print(i)\n",
" def __init__(self, init_state=None, model=None):\n",
" \"\"\"\n",
" Initialize a new Field instance for the 8 Queens problem.\n",
" \n",
" Args:\n",
" init_state (list, optional): Initial queen positions [1-8] for a number of columns.\n",
" If None, generates random positions.\n",
" model (str, optional): Solution method to use (\"genetic\" or \"backtrack\").\n",
" \"\"\"\n",
" self.state = []\n",
" self.domain_values = [1, 2, 3, 4, 5, 6, 7, 8]\n",
"\n",
" if init_state is None:\n",
" self.move_all_queens()\n",
" else:\n",
" self.state = init_state.copy()\n",
" \n",
" def print_field(self):\n",
" for row in range(8):\n",
" print(row)\n",
" for line in range(8):\n",
" if row+1 is self.state[line]:\n",
" print(\"Q\")\n",
" else:\n",
" print(\" \")\n",
" print(\"\\n\")\n",
" \n",
" self.move_all_queens(init_state)\n",
"\n",
" self.threats = self.collisions(self.state)\n",
" self.fitness = 28 - self.threats\n",
"\n",
" self.model = model\n",
"\n",
" def get_fitness(self):\n",
" \"\"\"\n",
" Returns the fitness value of the current state.\n",
" \n",
" Returns:\n",
" int: Fitness\n",
" \"\"\"\n",
" return self.fitness\n",
"\n",
" def get_state(self):\n",
" return self.state"
],
"outputs": [],
"execution_count": 2
" \"\"\"\n",
" Returns the current state representation.\n",
" \n",
" Returns:\n",
" list: List of queen row positions for each column [1-8].\n",
" \"\"\"\n",
" return self.state\n",
"\n",
" def get_domain_values(self):\n",
" \"\"\"\n",
" Returns the current domain values for constraint satisfaction.\n",
" \n",
" Returns:\n",
" list: Available row positions for queen placement in next column.\n",
" \"\"\"\n",
" return self.domain_values\n",
"\n",
" # Actions\n",
" def set_state(self, column, row=None):\n",
" \"\"\"\n",
" Sets the queen position for a specific column and updates fitness and threats.\n",
" \n",
" Args:\n",
" column (int): Column index [0-7] to place queen.\n",
" row (int, optional): Row position [1-8]. If None, places randomly.\n",
" \"\"\"\n",
" if row is None:\n",
" if column == len(self.state):\n",
" self.state.append(random.randint(1, 8))\n",
" elif 0 < row < 9:\n",
" if column < len(self.state):\n",
" self.state[column] = row\n",
" elif column == len(self.state):\n",
" self.state.append(row)\n",
"\n",
" self.threats = self.collisions()\n",
" self.fitness = 28 - self.threats\n",
"\n",
" def set_domain_values(self, new_domain):\n",
" \"\"\"\n",
" Updates the domain values for constraint satisfaction.\n",
" \n",
" Args:\n",
" new_domain (list): New set of valid row positions.\n",
" \"\"\"\n",
" self.domain_values = new_domain\n",
"\n",
" def add_queen(self, row):\n",
" \"\"\"\n",
" Adds a new queen to the next available column.\n",
" \n",
" Args:\n",
" row (int): Row position [1-8] for the new queen.\n",
" \"\"\"\n",
" if len(self.get_state()) < 8:\n",
" self.set_state(len(self.get_state()), row)\n",
"\n",
" def move_queen(self, column, new_row=None):\n",
" \"\"\"\n",
" Moves a queen to a new row position.\n",
" \n",
" Args:\n",
" column (int): Column index [0-7] of queen to move.\n",
" new_row (int, optional): New row position [1-8]. If None, moves randomly.\n",
" \"\"\"\n",
" if column <= len(self.get_state()):\n",
" self.set_state(column, new_row)\n",
"\n",
" def move_all_queens(self, new_state=None):\n",
" \"\"\"\n",
" Moves all queens to new positions.\n",
" \n",
" Args:\n",
" new_state (list, optional): Complete new state configuration.\n",
" If None, moves all queens randomly.\n",
" \"\"\"\n",
" if new_state is None:\n",
" for i in range(8):\n",
" self.move_queen(i)\n",
" else:\n",
" for i, new_row in enumerate(new_state):\n",
" self.move_queen(i, new_row)\n",
"\n",
" # heuristics functions\n",
" def collisions(self, current_state=None):\n",
" \"\"\"\n",
" Calculates the number of queen threats (heuristic function).\n",
" Counts horizontal and diagonal attacks between queens.\n",
" \n",
" Args:\n",
" current_state (list, optional): State to evaluate. Uses self.state if None.\n",
" \n",
" Returns:\n",
" float: Number of conflicting queen pairs.\n",
" \"\"\"\n",
" # wagerechte haben die gleiche row zahl stehe\n",
" # diagonale haben einen wert der um den spalten-abstand gemindert ist => gleichseitiges rechtwinkliges Dreieck\n",
" # Beachte die Spalten/ Linien Nr ist um eins verringert [0, 1, ...,7]\n",
" if current_state is None:\n",
" current_state = self.get_state()\n",
"\n",
" threats = 0\n",
" for i, row_i in enumerate(current_state):\n",
" for j, row_j in enumerate(current_state):\n",
" if j != i:\n",
" # horizontal diagonal in both sides up and down and counting \"twice\"\n",
" if row_i == row_j or row_j == (row_i + abs(j - i)) or row_j == (row_i - abs(j - i)):\n",
" threats += 1\n",
" # print(f\"{i+1}-{row_i} <=> {j+1}-{row_j}\") # Debugging\n",
" return threats / 2\n",
"\n",
" def print_field(self):\n",
" \"\"\"\n",
" Displays an ASCII representation of the chess board with queens.\n",
" \"\"\"\n",
" print(\"\\n ┌───┬───┬───┬───┬───┬───┬───┬───┐\")\n",
" for row in range(8, 0, -1): # (0:8]\n",
" row_string = \"\"\n",
" for line in range(8):\n",
" if line < len(self.state) and row == self.state[line]: # is there a Queen in this line (spalte) in this row\n",
" if (row + line) % 2 == 0:\n",
" row_string += \"▌Q▐│\"\n",
" else:\n",
" row_string += \" Q │\"\n",
"\n",
" elif (row + line) % 2 == 0:\n",
" row_string += \"███│\"\n",
" else:\n",
" row_string += \" │\"\n",
"\n",
" print(f\"{row} │{row_string}\")\n",
" if row > 1: print(\" ├───┼───┼───┼───┼───┼───┼───┼───┤\")\n",
"\n",
" print(\" └───┴───┴───┴───┴───┴───┴───┴───┘\")\n",
" print(\" A B C D E F G H \\n\")\n",
" print(f\"Threats: {self.threats}\")\n",
" print(f\"Fitness: {self.fitness}\")\n",
"\n",
" def calc(self):\n",
" \"\"\"\n",
" Executes the specified solution algorithm based on the model type.\n",
" Updates based on the selected model the current state with the solution and displays the result.\n",
" \"\"\"\n",
" if self.model == \"genetic\":\n",
" best_field = Genetic().calc()\n",
" self.move_all_queens(best_field.get_state())\n",
" self.print_field()\n",
"\n",
" elif self.model == \"backtrack\":\n",
" Backtrack().calc()"
]
},
{
"cell_type": "markdown",
"id": "10c258e5",
"metadata": {},
"source": [
"## Genetic Algorithm"
]
},
{
"cell_type": "code",
"id": "25dc1068",
"metadata": {
"ExecuteTime": {
"end_time": "2025-06-17T16:03:48.595773Z",
"start_time": "2025-06-17T16:03:48.593755Z"
}
},
"source": [
"def main():\n",
" new_field = Field()\n",
" new_field.print_field()\n",
"\n",
" print(\"Hello\")"
],
"execution_count": 24,
"id": "2ca3efc5",
"metadata": {},
"outputs": [],
"execution_count": 3
"source": [
"class Genetic:\n",
" \"\"\"\n",
" Implements genetic algorithm for solving the 8 Queens problem.\n",
" Uses fitness-based selection, crossover, and mutation operations.\n",
" \"\"\"\n",
" def __init__(self, size=1000):\n",
" \"\"\"\n",
" Initialize genetic algorithm with random population.\n",
" \n",
" Args:\n",
" size (int): Population size for the genetic algorithm.\n",
" \"\"\"\n",
" self.initial_population = []\n",
" self.p_mutation = 0.1\n",
"\n",
" for i in range(size):\n",
" self.initial_population.append(Field())\n",
"\n",
" def random_selection(self, population):\n",
" \"\"\"\n",
" Performs fitness-proportionate selection from population.\n",
" Higher fitness individuals have higher probability of selection.\n",
" Basierend auf der Verteilung der heuristischen Werte (Fitness) soll zufällig ein Eintrag (Field) gewählt werden, d.h. je höher der heuritische Wert (Fitness) ist, umso höher soll die Wahrscheinlichkeit sein, dass ein Field ausgewählt wird\n",
" \n",
" Args:\n",
" population (list): List of Field instances to select from.\n",
" \n",
" Returns:\n",
" Field: Selected individual based on fitness distribution.\n",
" \"\"\"\n",
" fitness = []\n",
" for field in population:\n",
" fitness.append(field.get_fitness())\n",
"\n",
" # Weighted random selection based on fitness values\n",
" chosen = random.choices(population, weights=fitness, k=1)[0]\n",
"\n",
" return chosen\n",
"\n",
" def mutation(self, field):\n",
" \"\"\"\n",
" Performs single random mutation on an individual by moving one queen.\n",
" \n",
" Args:\n",
" field (Field): an Individual to mutate.\n",
" \"\"\"\n",
" field.move_queen(random.randint(0, 7), random.randint(1, 8))\n",
"\n",
" def reproduce(self, x, y):\n",
" \"\"\"\n",
" Creates child individual through crossover of two parent individuals.\n",
" Uses single-point crossover at random position c.\n",
" \n",
" Args:\n",
" x (Field): First parent individual.\n",
" y (Field): Second parent individual.\n",
" \n",
" Returns:\n",
" Field: Child individual\n",
" \"\"\"\n",
" child = []\n",
" n = len(x.get_state())\n",
" c = random.randint(1, n)\n",
"\n",
" # Slice operator Syntax [a:b)\n",
" child.extend(x.get_state()[:c]) # [0:c)\n",
" child.extend(y.get_state()[c:]) # [c:end)\n",
"\n",
" return Field(child)\n",
" \n",
" def remove_unfit(self, population, limit):\n",
" new_population = []\n",
" for i, field in enumerate(population):\n",
" if field.get_fitness() >= limit:\n",
" new_population.append(field) \n",
" \n",
" # filling up the removed fields\n",
" if len(new_population) < len(population):\n",
" for i in range(len(population) - len(new_population)):\n",
" random_field = new_population[random.randint(0,len(new_population))]\n",
" new_population.append(random_field)\n",
" return new_population\n",
"\n",
"\n",
" def genetic_algorithm(self, n):\n",
" \"\"\"\n",
" Main genetic algorithm loop for evolving population.\n",
" \n",
" Args:\n",
" n (int): Maximum number of generations to run.\n",
" \n",
" Returns:\n",
" Field: Best individual found after n generations.\n",
" \"\"\"\n",
" current_population = self.initial_population\n",
" new_population = []\n",
" best_field = self.initial_population[0]\n",
"\n",
" for i in range(n):\n",
" current_population = self.remove_unfit(current_population, 14)\n",
" for j in range(len(current_population)):\n",
" x = self.random_selection(current_population)\n",
" y = self.random_selection(current_population)\n",
"\n",
" child = self.reproduce(x, y)\n",
"\n",
" if random.random() < self.p_mutation:\n",
" self.mutation(child)\n",
"\n",
" new_population.append(child)\n",
"\n",
" if child.get_fitness() > best_field.get_fitness():\n",
" best_field = child\n",
" if best_field.get_fitness() == 28:\n",
" break\n",
"\n",
" print(f\"{i} {best_field.get_state()} {best_field.get_fitness()}\")\n",
" if best_field.get_fitness() == 28:\n",
" break\n",
"\n",
" current_population = new_population\n",
" new_population = []\n",
"\n",
" return best_field\n",
"\n",
" def calc(self, n=100):\n",
" \"\"\"\n",
" Executes genetic algorithm for specified number of generations.\n",
" \n",
" Args:\n",
" n (int): Number of generations to evolve (default: 100).\n",
" \n",
" Returns:\n",
" Field: Best solution found by genetic algorithm.\n",
" \"\"\"\n",
" best_genetic_field = self.genetic_algorithm(n)\n",
" return best_genetic_field"
]
},
{
"cell_type": "markdown",
"id": "bc171a9e",
"metadata": {},
"source": [
"## Backtracking"
]
},
{
"cell_type": "code",
"execution_count": 25,
"id": "14a6ab56",
"metadata": {},
"outputs": [],
"source": [
"class Backtrack:\n",
" \"\"\"\n",
" Implements backtracking search for finding all solutions to 8 Queens problem.\n",
" Uses constraint satisfaction with consistency checking and inference.\n",
" \"\"\"\n",
"\n",
" def __init__(self):\n",
" \"\"\"\n",
" Initialize backtracking solver with empty results list.\n",
" \"\"\"\n",
" self.results = []\n",
"\n",
" def consistency(self, field, new_row):\n",
" \"\"\"\n",
" Checks if adding a queen at new_row maintains consistency.\n",
" Tests if the new configuration violates any constraints.\n",
" \n",
" Args:\n",
" field (Field): Current partial solution.\n",
" new_row (int): Row position for new queen.\n",
" \n",
" Returns:\n",
" bool: True if placement is consistent, False otherwise.\n",
" \"\"\"\n",
" current_state = field.get_state().copy()\n",
" current_state.append(new_row)\n",
" new_field = Field(current_state)\n",
"\n",
" if new_field.threats > 0:\n",
" return False\n",
" else:\n",
" return True\n",
"\n",
" def inference(self, field):\n",
" \"\"\"\n",
" Performs constraint propagation to reduce domain values.\n",
" Eliminates impossible row positions for the next column based on current state.\n",
" \n",
" Args:\n",
" field (Field): Current partial solution.\n",
" \n",
" Returns:\n",
" bool: True if domain is not empty, False if no valid moves remain.\n",
" \"\"\"\n",
" if len(field.get_state()) >= 8:\n",
" return True\n",
" \n",
" # Reset domain for current column\n",
" field.set_domain_values([1, 2, 3, 4, 5, 6, 7, 8]) \n",
" inferences = []\n",
"\n",
" for new_row in range(1, 9):\n",
" if not self.consistency(field, new_row):\n",
" inferences.append(new_row)\n",
"\n",
" for row in inferences:\n",
" if row in field.get_domain_values():\n",
" field.get_domain_values().remove(row)\n",
"\n",
" if len(field.get_domain_values()) == 0:\n",
" return False\n",
"\n",
" return True\n",
"\n",
" def backtracing(self, field):\n",
" \"\"\"\n",
" Recursive backtracking search to find all valid solutions.\n",
" \n",
" Args:\n",
" field (Field): Current partial solution.\n",
" \n",
" Returns:\n",
" list: List of complete solutions found from this state.\n",
" \"\"\"\n",
" if len(field.get_state()) == 8:\n",
" return [Field(field.get_state().copy())]\n",
"\n",
" solutions = []\n",
"\n",
" for row in field.get_domain_values():\n",
" # old_domain_values = field.get_domain_values().copy()\n",
" if self.consistency(field, row):\n",
" field.add_queen(row)\n",
" if self.inference(field): # nur für die nächste Spalte\n",
" result = self.backtracing(field)\n",
" if len(result) != 0:\n",
" solutions.extend(result)\n",
"\n",
" field.get_state().pop()\n",
" # field.domain_values = old_domain_values\n",
"\n",
" return solutions\n",
"\n",
" def calc(self):\n",
" for i in range(1, 9):\n",
" result = self.backtracing(Field([i]))\n",
" self.results.extend(result)\n",
" for i, result in enumerate(self.results):\n",
" print(f\"{i + 1} {result.get_state()}\")"
]
},
{
"cell_type": "code",
"execution_count": 26,
"id": "0ba915ff",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"------- Genetic -------\n",
"0 [7, 4, 1, 8, 6, 3, 7, 2] 27.0\n",
"1 [7, 4, 1, 8, 6, 3, 7, 2] 27.0\n",
"2 [7, 4, 1, 8, 6, 3, 7, 2] 27.0\n",
"3 [7, 4, 1, 8, 6, 3, 7, 2] 27.0\n",
"4 [7, 4, 1, 8, 6, 3, 7, 2] 27.0\n",
"5 [7, 4, 1, 8, 6, 3, 7, 2] 27.0\n",
"6 [7, 4, 1, 8, 6, 3, 7, 2] 27.0\n",
"7 [7, 4, 1, 8, 6, 3, 7, 2] 27.0\n",
"8 [7, 4, 1, 8, 6, 3, 7, 2] 27.0\n",
"9 [7, 4, 1, 8, 6, 3, 7, 2] 27.0\n",
"10 [7, 4, 1, 8, 6, 3, 7, 2] 27.0\n",
"11 [7, 4, 1, 8, 6, 3, 7, 2] 27.0\n",
"12 [7, 4, 1, 8, 6, 3, 7, 2] 27.0\n",
"13 [7, 4, 1, 8, 6, 3, 7, 2] 27.0\n",
"14 [7, 4, 1, 8, 6, 3, 7, 2] 27.0\n",
"15 [7, 4, 1, 8, 6, 3, 7, 2] 27.0\n",
"16 [7, 4, 1, 8, 6, 3, 7, 2] 27.0\n",
"17 [7, 4, 1, 8, 6, 3, 7, 2] 27.0\n",
"18 [7, 4, 1, 8, 6, 3, 7, 2] 27.0\n",
"19 [7, 4, 1, 8, 6, 3, 7, 2] 27.0\n",
"20 [7, 4, 1, 8, 6, 3, 7, 2] 27.0\n",
"21 [7, 4, 1, 8, 6, 3, 7, 2] 27.0\n",
"22 [7, 4, 1, 8, 6, 3, 7, 2] 27.0\n",
"23 [7, 4, 1, 8, 6, 3, 7, 2] 27.0\n",
"24 [3, 1, 7, 5, 8, 2, 4, 6] 28.0\n",
"\n",
" ┌───┬───┬───┬───┬───┬───┬───┬───┐\n",
"8 │███│ │███│ │▌Q▐│ │███│ │\n",
" ├───┼───┼───┼───┼───┼───┼───┼───┤\n",
"7 │ │███│ Q │███│ │███│ │███│\n",
" ├───┼───┼───┼───┼───┼───┼───┼───┤\n",
"6 │███│ │███│ │███│ │███│ Q │\n",
" ├───┼───┼───┼───┼───┼───┼───┼───┤\n",
"5 │ │███│ │▌Q▐│ │███│ │███│\n",
" ├───┼───┼───┼───┼───┼───┼───┼───┤\n",
"4 │███│ │███│ │███│ │▌Q▐│ │\n",
" ├───┼───┼───┼───┼───┼───┼───┼───┤\n",
"3 │ Q │███│ │███│ │███│ │███│\n",
" ├───┼───┼───┼───┼───┼───┼───┼───┤\n",
"2 │███│ │███│ │███│ Q │███│ │\n",
" ├───┼───┼───┼───┼───┼───┼───┼───┤\n",
"1 │ │▌Q▐│ │███│ │███│ │███│\n",
" └───┴───┴───┴───┴───┴───┴───┴───┘\n",
" A B C D E F G H \n",
"\n",
"Threats: 0.0\n",
"Fitness: 28.0\n"
]
}
],
"source": [
"def genetic():\n",
" print(\"------- Genetic -------\")\n",
" gen_field = Field(model=\"genetic\")\n",
" gen_field.calc()\n",
"\n",
"genetic()"
]
},
{
"cell_type": "code",
"execution_count": 27,
"id": "8be85fd2",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"------- Backtrack -------\n",
"1 [1, 5, 8, 6, 3, 7, 2, 4]\n",
"2 [1, 6, 8, 3, 7, 4, 2, 5]\n",
"3 [1, 7, 4, 6, 8, 2, 5, 3]\n",
"4 [1, 7, 5, 8, 2, 4, 6, 3]\n",
"5 [2, 4, 6, 8, 3, 1, 7, 5]\n",
"6 [2, 5, 7, 1, 3, 8, 6, 4]\n",
"7 [2, 5, 7, 4, 1, 8, 6, 3]\n",
"8 [2, 6, 1, 7, 4, 8, 3, 5]\n",
"9 [2, 6, 8, 3, 1, 4, 7, 5]\n",
"10 [2, 7, 3, 6, 8, 5, 1, 4]\n",
"11 [2, 7, 5, 8, 1, 4, 6, 3]\n",
"12 [2, 8, 6, 1, 3, 5, 7, 4]\n",
"13 [3, 1, 7, 5, 8, 2, 4, 6]\n",
"14 [3, 5, 2, 8, 1, 7, 4, 6]\n",
"15 [3, 5, 2, 8, 6, 4, 7, 1]\n",
"16 [3, 5, 7, 1, 4, 2, 8, 6]\n",
"17 [3, 5, 8, 4, 1, 7, 2, 6]\n",
"18 [3, 6, 2, 5, 8, 1, 7, 4]\n",
"19 [3, 6, 2, 7, 1, 4, 8, 5]\n",
"20 [3, 6, 2, 7, 5, 1, 8, 4]\n",
"21 [3, 6, 4, 1, 8, 5, 7, 2]\n",
"22 [3, 6, 4, 2, 8, 5, 7, 1]\n",
"23 [3, 6, 8, 1, 4, 7, 5, 2]\n",
"24 [3, 6, 8, 1, 5, 7, 2, 4]\n",
"25 [3, 6, 8, 2, 4, 1, 7, 5]\n",
"26 [3, 7, 2, 8, 5, 1, 4, 6]\n",
"27 [3, 7, 2, 8, 6, 4, 1, 5]\n",
"28 [3, 8, 4, 7, 1, 6, 2, 5]\n",
"29 [4, 1, 5, 8, 2, 7, 3, 6]\n",
"30 [4, 1, 5, 8, 6, 3, 7, 2]\n",
"31 [4, 2, 5, 8, 6, 1, 3, 7]\n",
"32 [4, 2, 7, 3, 6, 8, 1, 5]\n",
"33 [4, 2, 7, 3, 6, 8, 5, 1]\n",
"34 [4, 2, 7, 5, 1, 8, 6, 3]\n",
"35 [4, 2, 8, 5, 7, 1, 3, 6]\n",
"36 [4, 2, 8, 6, 1, 3, 5, 7]\n",
"37 [4, 6, 1, 5, 2, 8, 3, 7]\n",
"38 [4, 6, 8, 2, 7, 1, 3, 5]\n",
"39 [4, 6, 8, 3, 1, 7, 5, 2]\n",
"40 [4, 7, 1, 8, 5, 2, 6, 3]\n",
"41 [4, 7, 3, 8, 2, 5, 1, 6]\n",
"42 [4, 7, 5, 2, 6, 1, 3, 8]\n",
"43 [4, 7, 5, 3, 1, 6, 8, 2]\n",
"44 [4, 8, 1, 3, 6, 2, 7, 5]\n",
"45 [4, 8, 1, 5, 7, 2, 6, 3]\n",
"46 [4, 8, 5, 3, 1, 7, 2, 6]\n",
"47 [5, 1, 4, 6, 8, 2, 7, 3]\n",
"48 [5, 1, 8, 4, 2, 7, 3, 6]\n",
"49 [5, 1, 8, 6, 3, 7, 2, 4]\n",
"50 [5, 2, 4, 6, 8, 3, 1, 7]\n",
"51 [5, 2, 4, 7, 3, 8, 6, 1]\n",
"52 [5, 2, 6, 1, 7, 4, 8, 3]\n",
"53 [5, 2, 8, 1, 4, 7, 3, 6]\n",
"54 [5, 3, 1, 6, 8, 2, 4, 7]\n",
"55 [5, 3, 1, 7, 2, 8, 6, 4]\n",
"56 [5, 3, 8, 4, 7, 1, 6, 2]\n",
"57 [5, 7, 1, 3, 8, 6, 4, 2]\n",
"58 [5, 7, 1, 4, 2, 8, 6, 3]\n",
"59 [5, 7, 2, 4, 8, 1, 3, 6]\n",
"60 [5, 7, 2, 6, 3, 1, 4, 8]\n",
"61 [5, 7, 2, 6, 3, 1, 8, 4]\n",
"62 [5, 7, 4, 1, 3, 8, 6, 2]\n",
"63 [5, 8, 4, 1, 3, 6, 2, 7]\n",
"64 [5, 8, 4, 1, 7, 2, 6, 3]\n",
"65 [6, 1, 5, 2, 8, 3, 7, 4]\n",
"66 [6, 2, 7, 1, 3, 5, 8, 4]\n",
"67 [6, 2, 7, 1, 4, 8, 5, 3]\n",
"68 [6, 3, 1, 7, 5, 8, 2, 4]\n",
"69 [6, 3, 1, 8, 4, 2, 7, 5]\n",
"70 [6, 3, 1, 8, 5, 2, 4, 7]\n",
"71 [6, 3, 5, 7, 1, 4, 2, 8]\n",
"72 [6, 3, 5, 8, 1, 4, 2, 7]\n",
"73 [6, 3, 7, 2, 4, 8, 1, 5]\n",
"74 [6, 3, 7, 2, 8, 5, 1, 4]\n",
"75 [6, 3, 7, 4, 1, 8, 2, 5]\n",
"76 [6, 4, 1, 5, 8, 2, 7, 3]\n",
"77 [6, 4, 2, 8, 5, 7, 1, 3]\n",
"78 [6, 4, 7, 1, 3, 5, 2, 8]\n",
"79 [6, 4, 7, 1, 8, 2, 5, 3]\n",
"80 [6, 8, 2, 4, 1, 7, 5, 3]\n",
"81 [7, 1, 3, 8, 6, 4, 2, 5]\n",
"82 [7, 2, 4, 1, 8, 5, 3, 6]\n",
"83 [7, 2, 6, 3, 1, 4, 8, 5]\n",
"84 [7, 3, 1, 6, 8, 5, 2, 4]\n",
"85 [7, 3, 8, 2, 5, 1, 6, 4]\n",
"86 [7, 4, 2, 5, 8, 1, 3, 6]\n",
"87 [7, 4, 2, 8, 6, 1, 3, 5]\n",
"88 [7, 5, 3, 1, 6, 8, 2, 4]\n",
"89 [8, 2, 4, 1, 7, 5, 3, 6]\n",
"90 [8, 2, 5, 3, 1, 7, 4, 6]\n",
"91 [8, 3, 1, 6, 2, 5, 7, 4]\n",
"92 [8, 4, 1, 3, 6, 2, 7, 5]\n"
]
}
],
"source": [
"\n",
"def backtrack():\n",
" print(\"------- Backtrack -------\")\n",
" back_field = Field(model=\"backtrack\")\n",
" back_field.calc()\n",
"\n",
"backtrack()"
]
},
{
"cell_type": "code",
"execution_count": 28,
"id": "b5371c6e",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"------- My Field -------\n",
"\n",
" ┌───┬───┬───┬───┬───┬───┬───┬───┐\n",
"8 │███│ │███│ │███│ │███│ │\n",
" ├───┼───┼───┼───┼───┼───┼───┼───┤\n",
"7 │ │███│ │███│ Q │███│ │███│\n",
" ├───┼───┼───┼───┼───┼───┼───┼───┤\n",
"6 │███│ │███│ │███│ │▌Q▐│ │\n",
" ├───┼───┼───┼───┼───┼───┼───┼───┤\n",
"5 │ │▌Q▐│ │███│ │███│ │███│\n",
" ├───┼───┼───┼───┼───┼───┼───┼───┤\n",
"4 │███│ │▌Q▐│ │███│ │███│ │\n",
" ├───┼───┼───┼───┼───┼───┼───┼───┤\n",
"3 │ Q │███│ │███│ │▌Q▐│ │▌Q▐│\n",
" ├───┼───┼───┼───┼───┼───┼───┼───┤\n",
"2 │███│ │███│ Q │███│ │███│ │\n",
" ├───┼───┼───┼───┼───┼───┼───┼───┤\n",
"1 │ │███│ │███│ │███│ │███│\n",
" └───┴───┴───┴───┴───┴───┴───┴───┘\n",
" A B C D E F G H \n",
"\n",
"Threats: 5.0\n",
"Fitness: 23.0\n",
"[3, 5, 4, 2, 7, 3, 6, 3]\n",
"\n",
" ┌───┬───┬───┬───┬───┬───┬───┬───┐\n",
"8 │███│ │███│ │███│ │███│ │\n",
" ├───┼───┼───┼───┼───┼───┼───┼───┤\n",
"7 │ │███│ │███│ Q │███│ │███│\n",
" ├───┼───┼───┼───┼───┼───┼───┼───┤\n",
"6 │███│ │███│ │███│ │▌Q▐│ │\n",
" ├───┼───┼───┼───┼───┼───┼───┼───┤\n",
"5 │ │███│ │███│ │███│ │███│\n",
" ├───┼───┼───┼───┼───┼───┼───┼───┤\n",
"4 │███│ Q │▌Q▐│ │███│ │███│ │\n",
" ├───┼───┼───┼───┼───┼───┼───┼───┤\n",
"3 │ Q │███│ │███│ │▌Q▐│ │▌Q▐│\n",
" ├───┼───┼───┼───┼───┼───┼───┼───┤\n",
"2 │███│ │███│ Q │███│ │███│ │\n",
" ├───┼───┼───┼───┼───┼───┼───┼───┤\n",
"1 │ │███│ │███│ │███│ │███│\n",
" └───┴───┴───┴───┴───┴───┴───┴───┘\n",
" A B C D E F G H \n",
"\n",
"Threats: 8.0\n",
"Fitness: 20.0\n",
"[3, 4, 4, 2, 7, 3, 6, 3]\n"
]
}
],
"source": [
"\n",
"def main():\n",
" print(\"------- My Field -------\")\n",
" myField = Field()\n",
" myField.print_field()\n",
" print(myField.get_state())\n",
" myField.move_queen(1,4)\n",
" myField.print_field()\n",
" print(myField.get_state())\n",
"\n",
"main()"
]
}
],
"metadata": {

251
P2.py
View File

@@ -1,17 +1,27 @@
import random
from pickletools import read_uint1
class Field:
"""
Represents the 8 Queens problem state and provides methods for state manipulation. Encodes initial state, actions, transition model, and heuristic functions.
"""
def __init__(self, init_state=None, model=None):
"""
Initialize a new Field instance for the 8 Queens problem.
Args:
init_state (list, optional): Initial queen positions [1-8] for a number of columns.
If None, generates random positions.
model (str, optional): Solution method to use ("genetic" or "backtrack").
"""
self.state = []
self.domain_values = [1, 2, 3, 4, 5, 6, 7, 8]
if init_state is None:
for i in range(8):
self.state.append(random.randint(1, 8)) # row number [1:8]
self.move_all_queens()
else:
self.state = init_state.copy()
self.move_all_queens(init_state)
self.threats = self.collisions(self.state)
self.fitness = 28 - self.threats
@@ -19,38 +29,91 @@ class Field:
self.model = model
def get_fitness(self):
"""
Returns the fitness value of the current state.
Returns:
int: Fitness
"""
return self.fitness
def get_state(self):
"""
Returns the current state representation.
Returns:
list: List of queen row positions for each column [1-8].
"""
return self.state
def get_domain_values(self):
"""
Returns the current domain values for constraint satisfaction.
Returns:
list: Available row positions for queen placement in next column.
"""
return self.domain_values
# Actions
def set_state(self, column, row=None):
"""
Sets the queen position for a specific column and updates fitness and threats.
Args:
column (int): Column index [0-7] to place queen.
row (int, optional): Row position [1-8]. If None, places randomly.
"""
if row is None:
self.state[column] = random.randint(1, 8)
if column == len(self.state):
self.state.append(random.randint(1, 8))
elif 0 < row < 9:
if column < len(self.state):
self.state[column] = row
elif column == len(self.state):
self.state.append(row)
self.threats = self.collisions()
self.fitness = 28 - self.threats
def set_domain_values(self, new_domain):
"""
Updates the domain values for constraint satisfaction.
Args:
new_domain (list): New set of valid row positions.
"""
self.domain_values = new_domain
def add_queen(self, row):
"""
Adds a new queen to the next available column.
Args:
row (int): Row position [1-8] for the new queen.
"""
if len(self.get_state()) < 8:
self.set_state(len(self.get_state()), row)
def move_queen(self, column, new_row=None):
self.set_state(column, new_row)
# Update
self.threats = self.collisions()
self.fitness = 28 - self.threats
"""
Moves a queen to a new row position.
Args:
column (int): Column index [0-7] of queen to move.
new_row (int, optional): New row position [1-8]. If None, moves randomly.
"""
if column <= len(self.get_state()):
self.set_state(column, new_row)
def move_all_queens(self, new_state=None):
"""
Moves all queens to new positions.
Args:
new_state (list, optional): Complete new state configuration.
If None, moves all queens randomly.
"""
if new_state is None:
for i in range(8):
self.move_queen(i)
@@ -60,23 +123,36 @@ class Field:
# heuristics functions
def collisions(self, current_state=None):
"""
Calculates the number of queen threats (heuristic function).
Counts horizontal and diagonal attacks between queens.
Args:
current_state (list, optional): State to evaluate. Uses self.state if None.
Returns:
float: Number of conflicting queen pairs.
"""
# wagerechte haben die gleiche row zahl stehe
# diagonale haben einen wert der um den spalten-abstand gemindert ist => gleichseitiges rechtwinkliges Dreieck
# Beachte die Spalten/ Linien Nr ist um eins verringert [0, 1, ...,7]
if current_state is None:
current_state = self.get_state()
collisions = 0
threats = 0
for i, row_i in enumerate(current_state):
for j, row_j in enumerate(current_state):
if j is not i:
# horizontal diagonal in both sides up and down and counting "twice"
if row_i == row_j or row_j == (row_i + abs(j - i)) or row_j == (row_i - abs(j - i)):
collisions += 1
threats += 1
# print(f"{i+1}-{row_i} <=> {j+1}-{row_j}") # Debugging
return collisions / 2
return threats / 2
def print_field(self):
"""
Displays an ASCII representation of the chess board with queens.
"""
print("\n ┌───┬───┬───┬───┬───┬───┬───┬───┐")
for row in range(8, 0, -1): # (0:8]
row_string = ""
@@ -93,16 +169,22 @@ class Field:
else:
row_string += ""
print(f"{row} |{row_string}")
print(f"{row} {row_string}")
if row > 1: print(" ├───┼───┼───┼───┼───┼───┼───┼───┤")
print(" └───┴───┴───┴───┴───┴───┴───┴───┘")
print(" A B C D E F G H \n")
print(f"Threats: {self.threats}")
print(f"Fitness: {self.fitness}")
def calc(self):
"""
Executes the specified solution algorithm based on the model type.
Updates based on the selected model the current state with the solution and displays the result.
"""
if self.model == "genetic":
best_state = Genetic().calc()
self.state = best_state.get_state()
best_field = Genetic().calc()
self.move_all_queens(best_field.get_state())
self.print_field()
elif self.model == "backtrack":
@@ -110,7 +192,18 @@ class Field:
class Genetic:
"""
Implements genetic algorithm for solving the 8 Queens problem.
Uses fitness-based selection, crossover, and mutation operations.
"""
def __init__(self, size=1000):
"""
Initialize genetic algorithm with random population.
Args:
size (int): Population size for the genetic algorithm.
"""
self.initial_population = []
self.p_mutation = 0.1
@@ -119,55 +212,90 @@ class Genetic:
def random_selection(self, population):
"""
input:
population: a set of individuals
Fitness-FN: # of non-attacking queens (max 28)
returns:
Performs fitness-proportionate selection from population.
Higher fitness individuals have higher probability of selection.
Basierend auf der Verteilung der heuristischen Werte (Fitness) soll zufällig ein Eintrag (Field) gewählt werden, d.h. je höher der heuritische Wert (Fitness) ist, umso höher soll die Wahrscheinlichkeit sein, dass ein Field ausgewählt wird
Args:
population (list): List of Field instances to select from.
Returns:
Field: Selected individual based on fitness distribution.
"""
fitness = []
for field in population:
fitness.append(field.get_fitness())
# Weighted random selection based on fitness values
chosen = random.choices(population, weights=fitness, k=1)[0]
return chosen
def mutation(self, field):
"""
input:
state: a single individuals
returns:
randomly mutated version of it
Performs single random mutation on an individual by moving one queen.
Args:
field (Field): an Individual to mutate.
"""
field.move_queen(random.randint(0, 7), random.randint(1, 8))
def reproduce(self, x, y):
"""
Creates child individual through crossover of two parent individuals.
Uses single-point crossover at random position c.
Args:
x (Field): First parent individual.
y (Field): Second parent individual.
Returns:
Field: Child individual
"""
child = []
n = len(x.get_state())
c = random.randint(1, n)
child.extend(x.get_state()[:c]) # Slice operator Syntax [a:b[
child.extend(y.get_state()[c:])
# Slice operator Syntax [a:b)
child.extend(x.get_state()[:c]) # [0:c)
child.extend(y.get_state()[c:]) # [c:end)
return Field(child)
def remove_unfit(self, population, limit):
new_population = []
for i, field in enumerate(population):
if field.get_fitness() >= limit:
new_population.append(field)
population = new_population
def genetic_algorithm(self, n):
"""
population: a set of individuals
Fitness-FN: # of non-attacking queens (max 28)
Main genetic algorithm loop for evolving population.
Args:
n (int): Maximum number of generations to run.
Returns:
Field: Best individual found after n generations.
"""
current_population = self.initial_population
new_population = []
best_field = self.initial_population[0]
for i in range(n):
for j in range(len(self.initial_population)):
self.remove_unfit(current_population, 14)
for j in range(len(current_population)):
x = self.random_selection(current_population)
y = self.random_selection(current_population)
child = self.reproduce(x, y)
if random.random() < self.p_mutation:
self.mutation(child)
new_population.append(child)
if child.get_fitness() > best_field.get_fitness():
@@ -185,15 +313,43 @@ class Genetic:
return best_field
def calc(self, n=100):
"""
Executes genetic algorithm for specified number of generations.
Args:
n (int): Number of generations to evolve (default: 100).
Returns:
Field: Best solution found by genetic algorithm.
"""
best_genetic_field = self.genetic_algorithm(n)
return best_genetic_field
class Backtrack:
"""
Implements backtracking search for finding all solutions to 8 Queens problem.
Uses constraint satisfaction with consistency checking and inference.
"""
def __init__(self):
"""
Initialize backtracking solver with empty results list.
"""
self.results = []
def consistency(self, field, new_row):
"""
Checks if adding a queen at new_row maintains consistency.
Tests if the new configuration violates any constraints.
Args:
field (Field): Current partial solution.
new_row (int): Row position for new queen.
Returns:
bool: True if placement is consistent, False otherwise.
"""
current_state = field.get_state().copy()
current_state.append(new_row)
new_field = Field(current_state)
@@ -204,13 +360,22 @@ class Backtrack:
return True
def inference(self, field):
"""
Performs constraint propagation to reduce domain values.
Eliminates impossible row positions for the next column based on current state.
Args:
field (Field): Current partial solution.
Returns:
bool: True if domain is not empty, False if no valid moves remain.
"""
if len(field.get_state()) >= 8:
return True
field.set_domain_values([1, 2, 3, 4, 5, 6, 7, 8]) # Reset für jede Spalte
# Reset domain for current column
field.set_domain_values([1, 2, 3, 4, 5, 6, 7, 8])
inferences = []
# print(field.get_state())
# print(field.get_domain_values())
for new_row in range(1, 9):
if not self.consistency(field, new_row):
@@ -220,22 +385,28 @@ class Backtrack:
if row in field.get_domain_values():
field.get_domain_values().remove(row)
# print(inferences)
# print(f"{field.get_domain_values()}\n")
if len(field.get_domain_values()) == 0:
return False
return True
def backtracing(self, field):
"""
Recursive backtracking search to find all valid solutions.
Args:
field (Field): Current partial solution.
Returns:
list: List of complete solutions found from this state.
"""
if len(field.get_state()) == 8:
return [Field(field.get_state().copy())]
solutions = []
for row in field.get_domain_values():
old_domain_values = field.get_domain_values().copy()
# old_domain_values = field.get_domain_values().copy()
if self.consistency(field, row):
field.add_queen(row)
if self.inference(field): # nur für die nächste Spalte
@@ -244,7 +415,7 @@ class Backtrack:
solutions.extend(result)
field.get_state().pop()
field.domain_values = old_domain_values
# field.domain_values = old_domain_values
return solutions
@@ -257,14 +428,22 @@ class Backtrack:
def main():
print("------- Genetic -------")
gen_field = Field(model="genetic")
gen_field.calc()
print("\n------- Backtrack -------")
back_field = Field(model="backtrack")
back_field.calc()
print("\n------- My Field -------")
myField = Field()
myField.print_field()
print(myField.get_state())
myField.move_queen(1, 4)
myField.print_field()
print(myField.get_state())
main()