diff --git a/LinkedIn_Queens.ipynb b/LinkedIn_Queens.ipynb new file mode 100644 index 0000000..3cdf6ff --- /dev/null +++ b/LinkedIn_Queens.ipynb @@ -0,0 +1,513 @@ +{ + "nbformat": 4, + "nbformat_minor": 0, + "metadata": { + "colab": { + "provenance": [], + "authorship_tag": "ABX9TyPae0BfLCn8s92IA5hWBsqg", + "include_colab_link": true + }, + "kernelspec": { + "name": "python3", + "display_name": "Python 3" + }, + "language_info": { + "name": "python" + } + }, + "cells": [ + { + "cell_type": "markdown", + "metadata": { + "id": "view-in-github", + "colab_type": "text" + }, + "source": [ + "\"Open" + ] + }, + { + "cell_type": "markdown", + "source": [ + "![](https://static.licdn.com/aero-v1/sc/h/cxvywhn0ycytnmnfjsas8olhn)\n", + "\n", + "https://www.linkedin.com/games/queens/\n", + "\n", + "* The goal is to have exactly one queen in each row, column, and color region.\n", + "* Two queens cannot touch each other, not even diagonally.\n" + ], + "metadata": { + "id": "L8OaDzhFe-mn" + } + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/", + "height": 133 + }, + "id": "y4dz5SR_e1rR", + "outputId": "00978d36-53f3-4af6-86fc-b2f431060f39" + }, + "outputs": [ + { + "output_type": "stream", + "name": "stdout", + "text": [ + "Installing Conjure version v2.5.1 and Conjure Notebook version v0.0.10...\n", + "Downloading...\n", + "Conjure: The Automated Constraint Modelling Tool\n", + "Release version 2.5.1\n", + "Repository version a9cbc2e (2023-11-07 23:44:00 +0000)\n" + ] + }, + { + "output_type": "display_data", + "data": { + "text/plain": [ + "" + ], + "application/javascript": [ + "\"use strict\";\n", + "\n", + "CodeMirror.defineMode(\"text/conjure\", function (config) {\n", + "\n", + " var isOperatorChar = /[+\\-*=<>%^\\/]/;\n", + "\n", + " var keywords = {\n", + " \"forall\": true,\n", + " \"allDifferent\": true,\n", + " \"allDiff\": true,\n", + " \"alldifferent_except\": true,\n", + " \"dim\": true,\n", + " \"toSet\": true,\n", + " \"toMSet\": true,\n", + " \"toRelation\": true,\n", + " \"maximising\": true,\n", + " \"minimising\": true,\n", + " \"forAll\": true,\n", + " \"exists\": true,\n", + " \"toInt\": true,\n", + " \"sum\": true,\n", + " \"be\": true,\n", + " \"bijective\": true,\n", + " \"bool\": true,\n", + " \"by\": true,\n", + " \"complete\": true,\n", + " \"defined\": true,\n", + " \"domain\": true,\n", + " \"in\": true,\n", + " \"or\": true,\n", + " \"and\": true,\n", + " \"false\": true,\n", + " \"find\": true,\n", + " \"from\": true,\n", + " \"function\": true,\n", + " \"given\": true,\n", + " \"image\": true,\n", + " \"indexed\": true,\n", + " \"injective\": true,\n", + " \"int\": true,\n", + " \"intersect\": true,\n", + " \"freq\": true,\n", + " \"lambda\": true,\n", + " \"language\": true,\n", + " \"letting\": true,\n", + " \"matrix\": true,\n", + " \"maxNumParts\": true,\n", + " \"maxOccur\": true,\n", + " \"maxPartSize\": true,\n", + " \"maxSize\": true,\n", + " \"minNumParts\": true,\n", + " \"minOccur\": true,\n", + " \"minPartSize\": true,\n", + " \"minSize\": true,\n", + " \"mset\": true,\n", + " \"numParts\": true,\n", + " \"of\": true,\n", + " \"partial\": true,\n", + " \"partition\": true,\n", + " \"partSize\": true,\n", + " \"preImage\": true,\n", + " \"quantifier\": true,\n", + " \"range\": true,\n", + " \"regular\": true,\n", + " \"relation\": true,\n", + " \"representation\": true,\n", + " \"set\": true,\n", + " \"size\": true,\n", + " \"subset\": true,\n", + " \"subsetEq\": true,\n", + " \"such\": true,\n", + " \"supset\": true,\n", + " \"supsetEq\": true,\n", + " \"surjective\": true,\n", + " \"that\": true,\n", + " \"together\": true,\n", + " \"enum\": true,\n", + " \"total\": true,\n", + " \"true\": true,\n", + " \"new\": true,\n", + " \"type\": true,\n", + " \"tuple\": true,\n", + " \"union\": true,\n", + " \"where\": true,\n", + " \"branching\": true,\n", + " \"on\": true\n", + " }; \n", + " var punc = \":;,.(){}[]\";\n", + "\n", + " function tokenBase(stream, state) {\n", + " var ch = stream.next();\n", + " if (ch == '\"') {\n", + " state.tokenize.push(tokenString);\n", + " return tokenString(stream, state);\n", + " }\n", + " if (/[\\d\\.]/.test(ch)) {\n", + " if (ch == \".\") {\n", + " stream.match(/^[0-9]+([eE][\\-+]?[0-9]+)?/);\n", + " } else if (ch == \"0\") {\n", + " stream.match(/^[xX][0-9a-fA-F]+/) || stream.match(/^0[0-7]+/);\n", + " } else {\n", + " stream.match(/^[0-9]*\\.?[0-9]*([eE][\\-+]?[0-9]+)?/);\n", + " }\n", + " return \"number\";\n", + " }\n", + " if (ch == \"/\") {\n", + " if (stream.eat(\"*\")) {\n", + " state.tokenize.push(tokenComment);\n", + " return tokenComment(stream, state);\n", + " }\n", + " }\n", + " if (ch == \"$\") {\n", + " stream.skipToEnd();\n", + " return \"comment\";\n", + " }\n", + " if (isOperatorChar.test(ch)) {\n", + " stream.eatWhile(isOperatorChar);\n", + " return \"operator\";\n", + " }\n", + " if (punc.indexOf(ch) > -1) {\n", + " return \"punctuation\";\n", + " }\n", + " stream.eatWhile(/[\\w\\$_\\xa1-\\uffff]/);\n", + " var cur = stream.current();\n", + " \n", + " if (keywords.propertyIsEnumerable(cur)) {\n", + " return \"keyword\";\n", + " }\n", + " return \"variable\";\n", + " }\n", + "\n", + " function tokenComment(stream, state) {\n", + " var maybeEnd = false, ch;\n", + " while (ch = stream.next()) {\n", + " if (ch == \"/\" && maybeEnd) {\n", + " state.tokenize.pop();\n", + " break;\n", + " }\n", + " maybeEnd = (ch == \"*\");\n", + " }\n", + " return \"comment\";\n", + " }\n", + "\n", + " function tokenUntilClosingParen() {\n", + " var depth = 0;\n", + " return function (stream, state, prev) {\n", + " var inner = tokenBase(stream, state, prev);\n", + " console.log(\"untilClosing\", inner, stream.current());\n", + " if (inner == \"punctuation\") {\n", + " if (stream.current() == \"(\") {\n", + " ++depth;\n", + " } else if (stream.current() == \")\") {\n", + " if (depth == 0) {\n", + " stream.backUp(1)\n", + " state.tokenize.pop()\n", + " return state.tokenize[state.tokenize.length - 1](stream, state)\n", + " } else {\n", + " --depth;\n", + " }\n", + " }\n", + " }\n", + " return inner;\n", + " }\n", + " }\n", + "\n", + " function tokenString(stream, state) {\n", + " var escaped = false, next, end = false;\n", + " while ((next = stream.next()) != null) {\n", + " if (next == '(' && escaped) {\n", + " state.tokenize.push(tokenUntilClosingParen());\n", + " return \"string\";\n", + " }\n", + " if (next == '\"' && !escaped) { end = true; break; }\n", + " escaped = !escaped && next == \"\\\\\";\n", + " }\n", + " if (end || !escaped)\n", + " state.tokenize.pop();\n", + " return \"string\";\n", + " }\n", + "\n", + " return {\n", + " startState: function (basecolumn) {\n", + " return {\n", + " tokenize: []\n", + " };\n", + " },\n", + "\n", + " token: function (stream, state) {\n", + " if (stream.eatSpace()) return null;\n", + " var style = (state.tokenize[state.tokenize.length - 1] || tokenBase)(stream, state);\n", + " console.log(\"token\", style);\n", + " return style;\n", + " },\n", + "\n", + " blockCommentStart: \"/*\",\n", + " blockCommentEnd: \"*/\",\n", + " lineComment: \"$\"\n", + " };\n", + "});\n", + "\n", + "\n", + "CodeMirror.defineMIME(\"text/conjure\", \"text/conjure\");\n", + "\n", + "require(['notebook/js/codecell'], function (codecell) {\n", + " codecell.CodeCell.options_default.highlight_modes['magic_text/conjure'] = { 'reg': [/%?%conjure/] };\n", + " Jupyter.notebook.events.one('kernel_ready.Kernel', function () {\n", + " Jupyter.notebook.get_cells().map(function (cell) {\n", + " if (cell.cell_type == 'code') { cell.auto_highlight(); }\n", + " });\n", + " });\n", + "});\n", + "\n" + ] + }, + "metadata": {} + }, + { + "output_type": "display_data", + "data": { + "text/plain": [ + "" + ], + "text/markdown": "Conjure extension is loaded - run `%conjure_help`" + }, + "metadata": {} + } + ], + "source": [ + "!source <(curl -s https://raw.githubusercontent.com/conjure-cp/conjure-notebook/v0.0.10/scripts/install-colab.sh)\n", + "%reload_ext conjure" + ] + }, + { + "cell_type": "markdown", + "source": [ + "We initialise a python variable `regions` which contains five lists, each list containing the cell coordinates of the squares in the above image, belonging to a certain region." + ], + "metadata": { + "id": "zhGq3QVeiLpE" + } + }, + { + "cell_type": "code", + "source": [ + "regions = [\n", + " [(1,1)],\n", + " [(1,2), (1,3), (1,4), (1,5), (2,4)],\n", + " [(2,2), (3,2)],\n", + " [(4,4), (4,5)],\n", + " [\n", + " (2,1), (2,3), (2,5),\n", + " (3,1), (3,3), (3,4), (3,5),\n", + " (4,1), (4,2), (4,3),\n", + " (5,1), (5,2), (5,3), (5,4), (5,5)\n", + " ]\n", + " ]\n", + "\n", + "region_colours = [\"purple\", \"orange\", \"lightgreen\", \"gray\", \"cyan\"]\n", + "\n", + "n = len(regions)" + ], + "metadata": { + "id": "RO09ivcVhTcG" + }, + "execution_count": 42, + "outputs": [] + }, + { + "cell_type": "code", + "source": [ + "%%conjure\n", + "given n : int\n", + "given regions : matrix indexed by [int(1..n)] of set of tuple(int(1..n), int(1..n))\n", + "\n", + "find queens : matrix indexed by [int(1..n), int(1..n)] of bool\n", + "\n", + "such that\n", + " forAll r : int(1..n) .\n", + " sum([ toInt(queens[r,c]) | c : int(1..n) ]) = 1,\n", + "\n", + " forAll c : int(1..n) .\n", + " sum([ toInt(queens[r,c]) | r : int(1..n) ]) = 1,\n", + "\n", + " forAll reg : int(1..n) .\n", + " sum([ toInt(queens[r,c]) | (r,c) <- regions[reg] ]) = 1" + ], + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/", + "height": 337 + }, + "id": "pqDM6vA_gQHH", + "outputId": "ecd34103-3fef-4d87-dadc-94ffe72f887c" + }, + "execution_count": 69, + "outputs": [ + { + "output_type": "display_data", + "data": { + "text/plain": [ + "" + ], + "text/markdown": "```json\n{\"queens\": {\"1\": {\"1\": true, \"2\": false, \"3\": false, \"4\": false, \"5\": false}, \"2\": {\"1\": false, \"2\": false, \"3\": false, \"4\": true, \"5\": false}, \"3\": {\"1\": false, \"2\": true, \"3\": false, \"4\": false, \"5\": false}, \"4\": {\"1\": false, \"2\": false, \"3\": false, \"4\": false, \"5\": true}, \"5\": {\"1\": false, \"2\": false, \"3\": true, \"4\": false, \"5\": false}}}\n```" + }, + "metadata": {} + }, + { + "output_type": "display_data", + "data": { + "text/plain": [ + "" + ], + "text/markdown": "| Statistic | Value |\n|:-|-:|\n| SolverTotalTime | 0.000 |\n| SavileRowClauseOut | 0 |\n| SavileRowTotalTime | 0.182 |\n| SolverFailures | 0 |\n| SolverSatisfiable | 1 |\n| SavileRowTimeOut | 0 |\n| SolverTimeOut | 0 |\n| SolverNodes | 1 |\n" + }, + "metadata": {} + } + ] + }, + { + "cell_type": "code", + "source": [ + "# quickly view the solutions\n", + "print(*[ \"\".join([(\"▯\", \"Q\")[x] for x in list(conjure_solutions[0]['queens'][str(i)].values())]) for i in range(1, 6)], sep=\"\\n\")" + ], + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" + }, + "id": "ywg6gh42ioHN", + "outputId": "9b8b83a8-353c-42ae-d095-485e426301ac" + }, + "execution_count": 70, + "outputs": [ + { + "output_type": "stream", + "name": "stdout", + "text": [ + "Q▯▯▯▯\n", + "▯▯▯Q▯\n", + "▯Q▯▯▯\n", + "▯▯▯▯Q\n", + "▯▯Q▯▯\n" + ] + } + ] + }, + { + "cell_type": "code", + "source": [ + "queen_coords = []\n", + "for i in range(1, n+1):\n", + " for j in range(1, n+1):\n", + " if queens[str(i)][str(j)]:\n", + " queen_coords.append((i,j))\n", + "\n", + "queen_coords" + ], + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" + }, + "id": "VtZYwHaODB4R", + "outputId": "dd4b5df3-f37a-4c86-ab94-1ae30e87fd86" + }, + "execution_count": 53, + "outputs": [ + { + "output_type": "execute_result", + "data": { + "text/plain": [ + "[(1, 1), (2, 4), (3, 2), (4, 5), (5, 3)]" + ] + }, + "metadata": {}, + "execution_count": 53 + } + ] + }, + { + "cell_type": "markdown", + "source": [ + "Finally use matplotlib to display the solved queens puzzle prettily" + ], + "metadata": { + "id": "AJvaz7oyFR5J" + } + }, + { + "cell_type": "code", + "source": [ + "import matplotlib.pyplot as plt\n", + "from matplotlib.patches import Rectangle\n", + "import numpy as np\n", + "\n", + "%matplotlib inline\n", + "\n", + "square_size = 1\n", + "fig, ax = plt.subplots()\n", + "\n", + "for y in range(1, n+1):\n", + " for x in range(1, n+1):\n", + " region_num = [(y, x) in region for region in regions].index(True)\n", + " square = Rectangle((x-1, n-y),\n", + " square_size, square_size,\n", + " edgecolor='black', facecolor=region_colours[region_num])\n", + " ax.add_patch(square)\n", + " if (y, x) in queen_coords:\n", + " ax.text(x-square_size/2,n-y+square_size/2, \"♕\", ha=\"center\", va=\"center\", fontsize=30)\n", + "\n", + "ax.set_xlim(0, n)\n", + "ax.set_ylim(0, n)\n", + "ax.set_xticks([])\n", + "ax.set_yticks([])\n", + "ax.set_aspect('equal')\n", + "ax.grid(True)\n", + "plt.show()" + ], + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/", + "height": 406 + }, + "id": "jSS636x__YuT", + "outputId": "2e4cf667-cdad-4a4e-e174-be264c0ce60a" + }, + "execution_count": 71, + "outputs": [ + { + "output_type": "display_data", + "data": { + "text/plain": [ + "
" + ], + "image/png": "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\n" + }, + "metadata": {} + } + ] + } + ] +} \ No newline at end of file