{
 "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
}
