Skip to content
Snippets Groups Projects
Verified Commit 6138d4dd authored by Maarten van den Berg's avatar Maarten van den Berg
Browse files

Change approach to be more efficient for {first,last}-fit + hook up CLI

parent ac22c1cb
No related branches found
No related tags found
No related merge requests found
......@@ -10,9 +10,9 @@ from cinematinator.instance_io import (
parse_group_counts,
prettyprint_cinema,
)
from cinematinator.online_test import TestOnlineSolver
from cinematinator.offline import BruteforceOfflineSolver
from cinematinator.mip_test import MIPSolver
from cinematinator.offline import BruteforceOfflineSolver
from cinematinator.online import online_solvers
from cinematinator.simple_cinema import SimpleCinema
from cinematinator.types import OfflineSolverProtocol
......@@ -85,14 +85,39 @@ def edge_algo_cmd(input_file: TextIO) -> None:
@cli.command("online")
@click.argument("input_file", type=click.File(mode="r"))
def online_cmd(input_file: TextIO) -> None:
@click.option(
"--mode",
"mode_name",
type=click.Choice(online_solvers.keys()),
default="first-fit",
help="Which seat assignment mode to use.",
)
@click.option(
"--print-when-done/--no-print-when-done",
"-p",
default=False,
help="Print the final Cinema when input ends?",
)
def online_cmd(input_file: TextIO, mode_name: str) -> None:
"""
Online dingos
Solve an instance of the online variant of the problem.
Currently the following modes are supported with --mode:
\b
- "first-fit":
Fit groups in the leftmost position in the top row where the group fits.
- "last-fit":
Fit groups in the rightmost position in the bottom row where the group fits.
- "greedy":
Fit groups in a position where they cause a minimum amount of still-available
seats to be blocked.
- "center":
Try to fit groups as closest to the center of the cinema as possible.
"""
cinema = parse_cinema(input_file)
solver = TestOnlineSolver(cinema)
solver = online_solvers[mode_name](cinema)
for line in input_file:
input = line.strip()
......
from __future__ import annotations
from functools import partial
from math import ceil
from typing import Callable, Dict, List, Optional, Tuple
from cinematinator.types import Cinema, Coordinates, OnlineSolverProtocol
GroupSize = int
Heuristic = Callable[[Cinema, Coordinates, GroupSize], int]
online_solvers: Dict[str, Callable[[Cinema], OnlineSolverProtocol]] = {
"first-fit": lambda c: OnlineFirstFitSolver(c),
"last-fit": lambda c: OnlineLastFitSolver(c),
"greedy": lambda c: OnlineHeuristicSolver(c, heuristic=greedy_heuristic),
"center": lambda c: OnlineHeuristicSolver(c, heuristic=center_heuristic),
}
class OnlineFirstFitSolver:
def __init__(self, cinema: Cinema) -> None:
self.cinema = cinema
def try_seat_group(self, group_size: GroupSize) -> Optional[Coordinates]:
for y in range(self.cinema.height):
for x in range(self.cinema.width):
if self.cinema.can_seat_group(x, y, group_size):
self.cinema.seat_group(x, y, group_size)
return (x, y)
return None
class OnlineLastFitSolver:
def __init__(self, cinema: Cinema) -> None:
self.cinema = cinema
def try_seat_group(self, group_size: GroupSize) -> Optional[Coordinates]:
for y in reversed(range(self.cinema.height)):
for x in reversed(range(self.cinema.width)):
if self.cinema.can_seat_group(x, y, group_size):
self.cinema.seat_group(x, y, group_size)
return (x, y)
return None
class OnlineHeuristicSolver:
def __init__(self, cinema: Cinema, heuristic: Heuristic) -> None:
self.cinema = cinema
self.heuristic = heuristic
def try_seat_group(self, group_size: GroupSize) -> Optional[Coordinates]:
potential_locations: List[Tuple[int, Coordinates]] = []
for y in range(self.cinema.height):
potential_locations.extend(self.scan_row(group_size, y))
potential_locations.sort()
if len(potential_locations) == 0:
return None
penalty, optimal_location = potential_locations[0]
seat_at_x, seat_at_y = optimal_location
self.cinema.seat_group(seat_at_x, seat_at_y, group_size)
return optimal_location
def scan_row(self, group_size: GroupSize, y: int) -> List[Tuple[int, Coordinates]]:
"""
Scan the row at the given height for open spots that fit the given group size, then
return the starting location that fits the group while returning the smallest
penalty as defined by the passed heuristic.
If there are multiple eligible locations that have the same penalty, return the
top-leftmost position.
"""
potential_locations: List[Tuple[int, Coordinates]] = []
# Optimization: we will never be able to seat a group at a position less than
# group_size from the right edge.
for x in range(0, self.cinema.width - group_size + 1):
if self.cinema.can_seat_group(x, y, group_size):
penalty = self.heuristic(self.cinema, (x, y), group_size)
potential_locations.append((penalty, (x, y)))
return potential_locations
def greedy_heuristic(cinema: Cinema, spot: Coordinates, group_size: GroupSize) -> int:
x, y = spot
return cinema.newly_blocked_seats(x, y, group_size)
def center_heuristic(cinema: Cinema, spot: Coordinates, group_size: GroupSize) -> int:
x, y = spot
left_edge = x
right_edge = x + group_size
center = cinema.width / 2
center_difference = min(abs(left_edge - center), abs(right_edge - center))
return ceil(center_difference)
from __future__ import annotations
from typing import Optional, Tuple, List
from cinematinator.types import Cinema, Coordinates
class TestOnlineSolver:
def __init__(self, cinema: Cinema) -> None:
self.cinema = cinema
def try_seat_group(self, group_size: int) -> Optional[Coordinates]:
potential_locations: List[Tuple[int, Coordinates]] = []
for y in range(self.cinema.height):
potential_locations.extend(scan_row(self.cinema, group_size, y))
potential_locations.sort()
if len(potential_locations) == 0:
return None
penalty, optimal_location = potential_locations[0]
seat_at_x, seat_at_y = optimal_location
self.cinema.seat_group(seat_at_x, seat_at_y, group_size)
return optimal_location
def scan_row(cinema: Cinema, group_size: int, y: int) -> List[Tuple[int, Coordinates]]:
"""
Scan the row at the given height for open spots that fit the given group size, then
return the starting location that fits the group while blocking the smallest amount
of still-available seats.
If there are multiple eligible locations that block the same number of
still-available seats, return the leftmost position.
If there is such a spot, return a 2-tuple (blocked_seats, x).
If there is no such spot, return None.
"""
potential_locations: List[Tuple[int, Coordinates]] = []
# Optimalization: we will never be able to seat a group at a position less than
# group_size from the right edge.
for x in range(0, cinema.width - group_size + 1):
if cinema.can_seat_group(x, y, group_size):
penalty = cinema.newly_blocked_seats(x, y, group_size)
potential_locations.append((penalty, (x, y)))
return potential_locations
......@@ -180,8 +180,6 @@ class Cinema:
return result
class OfflineSolverProtocol(Protocol):
"""
Protocol for offline solvers.
......@@ -207,8 +205,7 @@ class OnlineSolverProtocol(Protocol):
this groups has been seated or None if the group cannot be seated.
"""
def __init__(self, initial_cinema: Cinema) -> None:
...
cinema: Cinema
def try_seat_group(self, group_size: int) -> Optional[Coordinates]:
...
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment