# Parking Lot

Design a parking system.

Purposefully vague. Discuss the exact requirement with your interviewer.

**Some questions we can ask:**

* How should accessibility be taken care of? Do we need to prioritize handcap parking areas, for example?
* Is this a ground parking lot, or a monitored system?
* How many entrances are in the parking lot? (Do we have to deal with any issues of concurancy?)
* Should there by any pricing strategies in place? Do we offer premium services (reserved parking areas) to customers?
* Do we need to support different types of spots for different type of vehicles?

**Assumptions:**

* Design a monitored system for support for 4 different type of vehicles (S,M,L,XL). The parking lot supports 150 small vehicles, 100 medium vehicles, 50 large vehicles, and 25 extra large vehicles.
* A smaller vehicle can only be parked in a spot that is designated to be equal or large than itself. That is a L vehicle cannot park to M spot, but can park to an L to XL spot.

**Implementation:**

* Build an interface and support efficient implementation for the following methods:
  * `Spot placeVehicle(Vehicle):`return a spot for a Vehicle spot.
    * This method must return an available spot prioritized by its type. For example, a small vehicle can only be parked in a mediumed sized parking spot iff there are no small vehicle spots available.
    * If there are no spots available, throw an exception.
  * `void removeVehicle(Vehicle):` remove a vehicle from the parking lot.

```python
from enum import IntEnum
import queue, copy


class Size(IntEnum):
    S = 0,
    M = 1,
    L = 2,
    XL = 3


class Vehicle:
    def __init__(self, id, size):
        """
        :param id: [int]
        :param size: [Size]
        """
        self.drivers_lic = id
        self.size = size

    def __hash__(self):
        return hash(self.drivers_lic)

    def __eq__(self, other):
        return self.drivers_lic == other.drivers_lic

    def __str__(self):
        return str(self.drivers_lic) + " " + str(self.size)


class Car(Vehicle):
    def __init__(self, id):
        """
        :param id: [int]
        """
        super().__init__(id, Size.M)

    def __hash__(self):
        return super().__hash__()

    def __eq__(self, other):
        return super().__eq__(other)

    def __str__(self):
        return super().__str__()


class MotorCycle(Vehicle):
    def __init__(self, id):
        """
        :param id: [int]
        """
        super().__init__(id, Size.S)

    def __hash__(self):
        return super().__hash__()

    def __eq__(self, other):
        return super().__eq__(other)

    def __str__(self):
        return super().__str__()


class Bus(Vehicle):
    def __init__(self, id):
        """
        :param id: [int]
        """
        super().__init__(id, Size.XL)

    def __hash__(self):
        return super().__hash__()

    def __eq__(self, other):
        return super().__eq__(other)

    def __str__(self):
        return super().__str__()


class Truck(Vehicle):
    def __init__(self, id):
        """
        :param id: [int]
        """
        super().__init__(id, Size.L)

    def __hash__(self):
        return super().__hash__()

    def __eq__(self, other):
        return super().__eq__(other)

    def __str__(self):
        return super().__str__()


class Spot:
    def __init__(self, id, size):
        """
        :param id: [int]
        :param size: [Size]
        """
        self.id = id
        self.size = size

    def __eq__(self, other):
        return self.id == other.id

class ParkingLot:
    def __init__(self, zipcode, size_class):
        """
        :param zipcode: [int]
        :param size_class: List[int]
        """
        self.zipcode = zipcode
        self.capacities = size_class
        self.counts = [0] * 4
        # main datastructure to hold are parking cars
        self.Park_DB = {Size.S: {}, Size.M: {}, Size.L: {}, Size.XL: {}}

        # using a priority queue will ensure that when a car gets
        # removed from the lot, the next available spot will be the
        # next 'closest' spot to the next user.
        self.spot_ids = queue.PriorityQueue()
        for id in range(sum(self.capacities)):
            self.spot_ids.put(id)

    def place_vehicle(self, vehicle):
        """
        Identifies and stores the next available parking spot
        :param vehicle: [Vehicle]
        :return: [Spot]
        """
        # check if vehicle already exists in the lot
        if any(vehicle in self.Park_DB[size] for size in Size):
            raise ValueError("Duplicate Vehicle ID identified.")

        # find the first available spot starting from current size
        for size in range(int(vehicle.size), len(self.counts)):
            if self.counts[size] < self.capacities[size]:
                # a spot was found, so add the spot to the hash table
                next_spot = Spot(self.spot_ids.get(), Size(size))
                self.Park_DB[Size(size)][vehicle] = next_spot
                self.counts[size] += 1
                return next_spot
        raise OverflowError("All vehicle capacities are full.")

    def remove_vehicle(self, vehicle):
        """
        Removes an identified vehicle from the parking lot
        :param vehicle: [vehicle]
        :return: [Spot]
        """
        for size in range(int(vehicle.size), len(self.counts)):
            if vehicle in self.Park_DB[Size(size)]:
                # the vehicle was found - copy and return its spot
                spot = copy.copy(self.Park_DB[Size(size)][vehicle])
                # put back the available parking id spot back into queue
                self.spot_ids.put(spot.id)
                # remove the vehicle from the lot
                del self.Park_DB[Size(size)][vehicle]
                self.counts[size] -= 1
                return spot
        # vehicle was not found
        raise LookupError("Vehicle id was unidentified in the ParkingLot")

    def __str__(self):
        strings = []
        for size, vehicles in self.Park_DB.items():
            strings.append(str(size) + ": " + "\n")
            for vehicle in vehicles:
                strings.append("\t" + str(vehicle) + "\n")
        return ''.join(strings)
```

**Testing**

```python
PL = ParkingLot(95616, [0, 2, 3, 1])
PL.place_vehicle(Car(0))
try:
    PL.place_vehicle(Car(0))
except ValueError:
    print("Duplicate identified")
PL.place_vehicle(Car(1))
spot1 = PL.place_vehicle(Car(2))
print(PL)
spot2 = PL.remove_vehicle(Car(2))
print(PL)
assert(spot1 == spot2)
try:
    PL.remove_vehicle(Car(2))
except LookupError:
    print("Vehicle doesnt Exist")
spot3 = PL.place_vehicle(Car(3))
assert(spot1 == spot3 == spot2)

PL.place_vehicle(Bus(20))
try:
    PL.place_vehicle(Bus(21))
except OverflowError:
    print("No more bus spots available")
print(PL)
```

**Output**

```
Duplicate identified
Size.S: 
Size.M: 
    0 Size.M
    1 Size.M
Size.L: 
    2 Size.M
Size.XL: 

Size.S: 
Size.M: 
    0 Size.M
    1 Size.M
Size.L: 
Size.XL: 

Vehicle doesnt Exist
No more bus spots available
Size.S: 
Size.M: 
    0 Size.M
    1 Size.M
Size.L: 
    3 Size.M
Size.XL: 
    20 Size.XL
```


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://maksimdan.gitbook.io/interview-practice-problems/leetcode_sessions/design/parking-lot.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
