formations/python-avancé/exercises/rectangle-packing.ipynb

195 lines
5.3 KiB
Plaintext

{
"cells": [
{
"cell_type": "code",
"execution_count": null,
"id": "54ae86a4",
"metadata": {},
"outputs": [],
"source": [
"from itertools import product, zip_longest, count\n",
"import numpy as np\n",
"from dataclasses import dataclass\n",
"from matplotlib import pyplot as plt"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "e4756abb",
"metadata": {},
"outputs": [],
"source": [
"@dataclass\n",
"class Rectangle:\n",
" x: int\n",
" y: int\n",
" w: int\n",
" h: int\n",
"\n",
"# Beware, axis 0 is vertical, axis 1 horizontal\n",
"\n",
"def measure(rectangles):\n",
" w, h = 0, 0\n",
" for rect in rectangles:\n",
" w = max(w, rect.x + rect.w)\n",
" h = max(h, rect.y + rect.h)\n",
" return w, h\n",
"\n",
"def check_overlap(rectangles):\n",
" w, h = measure(rectangles)\n",
" image = np.zeros((h, w), dtype=int)\n",
" for i, rect in enumerate(rectangles, start=1):\n",
" if rect.x < 0 or rect.y < 0:\n",
" raise ValueError(\"Rectangle placed out of bound\", rect)\n",
" for x, y in product(range(rect.w), range(rect.h)):\n",
" if image[rect.y + y, rect.x + x]:\n",
" raise ValueError(f\"Overlap at {rect.x+x}, {rect.y+y}\", rectangles[i - 1], rectangles[image[rect.y + y, rect.x + x] - 1])\n",
" image[rect.y + y, rect.x + x] = i\n",
"\n",
"def show_rectangles(rectangles):\n",
" w, h = measure(rectangles)\n",
" image = np.zeros((h, w), dtype=int)\n",
" for i, rect in enumerate(rectangles, start=1):\n",
" for x, y in product(range(rect.w), range(rect.h)):\n",
" image[rect.y + y, rect.x + x] = i\n",
" plt.imshow(image, aspect='equal')"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "fd8c423e",
"metadata": {},
"outputs": [],
"source": [
"r1 = Rectangle(0, 0, 10, 10)\n",
"r2 = Rectangle(0, 10, 10, 10)\n",
"r3 = Rectangle(10, 0, 10, 20)\n",
"show_rectangles([r1, r2, r3])"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "1c7bfa44",
"metadata": {},
"outputs": [],
"source": [
"r1 = Rectangle(0, 0, 10, 10)\n",
"r2 = Rectangle(0, 0, 5, 5)\n",
"try:\n",
" check_overlap([r1, r2])\n",
"except ValueError as err:\n",
" print(err)"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "ec3d4881",
"metadata": {},
"outputs": [],
"source": [
"def repack(to_pack):\n",
" to_pack.sort(key=lambda r: r.w)\n",
" total_height = sum(r.h for r in to_pack)\n",
" first_half = []\n",
" second_half = []\n",
" position = 0\n",
" for rect in to_pack:\n",
" if position < total_height // 2:\n",
" first_half.append(rect)\n",
" else:\n",
" second_half.append(rect)\n",
" position += rect.h\n",
" total_width = to_pack[-1].w + to_pack[-2].w\n",
" for shift in range(0, 1000):\n",
" y = 0\n",
" for left in first_half:\n",
" left.y = y\n",
" left.x = 0\n",
" y += left.h\n",
" for right in second_half:\n",
" right.y = y - right.h\n",
" right.x = shift - right.w\n",
" y -= right.h\n",
" try:\n",
" check_overlap(to_pack)\n",
" return\n",
" except ValueError:\n",
" pass"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "90689e9a",
"metadata": {},
"outputs": [],
"source": [
"to_pack = [\n",
" Rectangle(0, 0, 8, 10),\n",
" Rectangle(0, 0, 5, 6),\n",
" Rectangle(0, 0, 5, 4),\n",
" Rectangle(0, 0, 2, 5),\n",
"]\n",
"repack(to_pack)\n",
"show_rectangles(to_pack)\n",
"check_overlap(to_pack)\n",
"w, h = measure(to_pack)\n",
"print(\"score (lower is better)\", w * h)"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "b3778f5b",
"metadata": {},
"outputs": [],
"source": [
"to_pack = [\n",
" Rectangle(0, 0, 8, 10),\n",
" Rectangle(0, 0, 5, 6),\n",
" Rectangle(0, 0, 5, 4),\n",
" Rectangle(0, 0, 2, 5),\n",
" Rectangle(0, 0, 8, 16),\n",
" Rectangle(0, 0, 5, 5),\n",
" Rectangle(0, 0, 5, 2),\n",
" Rectangle(0, 0, 2, 3),\n",
" Rectangle(0, 0, 12, 10),\n",
" Rectangle(0, 0, 4, 6),\n",
" Rectangle(0, 0, 4, 4),\n",
" Rectangle(0, 0, 1, 5),\n",
"]\n",
"repack(to_pack)\n",
"show_rectangles(to_pack)\n",
"check_overlap(to_pack)\n",
"w, h = measure(to_pack)\n",
"print(\"score (lower is better)\", w * h)"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3 (ipykernel)",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.9.4"
}
},
"nbformat": 4,
"nbformat_minor": 5
}