5
\$\begingroup\$

I own a parking lot that can hold up to 'n' cars at any given point in time. Each slot is given a number starting at 1 increasing with increasing distance from the entry point in steps of one. I want to create an automated ticketing system that allows my customers to use my parking lot without human intervention.

When a car enters my parking lot, I want to have a ticket issued to the driver. The ticket issuing process includes us documenting the registration number (number plate) and the colour of the car and allocating an available parking slot to the car before actually handing over a ticket to the driver (we assume that our customers are nice enough to always park in the slots allocated to them). The customer should be allocated a parking slot which is nearest to the entry. At the exit the customer returns the ticket which then marks the slot they were using as being available.

Due to government regulation, the system should provide me with the ability to find out:

● Registration numbers of all cars of a particular colour.

● Slot number in which a car with a given registration number is parked.

● Slot numbers of all slots where a car of a particular colour is parked.

import heapq
from collections import defaultdict, OrderedDict


class Car:
    def __init__(self, registration_number, color):
        self.registration_number = registration_number
        self.color = color

    def __str__(self):
        return "Car [registration_number=" + self.registration_number + ", color=" + self.color + "]"


class ParkingLot:
    def __init__(self, total_slots):
        self.registration_slot_mapping = dict()
        self.color_registration_mapping = defaultdict(list)
        # we need to maintain the orders of cars while showing 'status'
        self.slot_car_mapping = OrderedDict()

        # initialize all slots as free
        self.available_parking_lots = []
        # Using min heap as this will always give minimun slot number in O(1) time
        for i in range(1, total_slots + 1):
            heapq.heappush(self.available_parking_lots, i)

    def status(self):
        for slot, car in self.slot_car_mapping.items():
            print("Slot no: {} {}".format(slot, car))

    def get_nearest_slot(self):
        return heapq.heappop(self.available_parking_lots) if self.available_parking_lots else None

    def free_slot(self, slot_to_be_freed):
        found = None
        for registration_no, slot in self.registration_slot_mapping.items():
            if slot == slot_to_be_freed:
                found = registration_no

        # Cleanup from all cache
        if found:
            del self.registration_slot_mapping[found]
            car_to_leave = self.slot_car_mapping[slot_to_be_freed]
            self.color_registration_mapping[car_to_leave.color].remove(found)
            del self.slot_car_mapping[slot_to_be_freed]
            print("leave ", slot_to_be_freed)
        else:
            print("slot is not in use")

    def park_car(self, car):
        slot_no = self.get_nearest_slot()
        if slot_no is None:
            print("Sorry, parking lot is full")
            return
        self.slot_car_mapping[slot_no] = car
        self.registration_slot_mapping[car.registration_number] = slot_no
        self.color_registration_mapping[car.color].append(car.registration_number)

    # ● Registration numbers of all cars of a particular colour.
    def get_registration_nos_by_color(self, color):
        return self.color_registration_mapping[color]

    # ● Slot numbers of all slots where a car of a particular colour is parked.
    def get_slot_numbers_by_color(self, color):
        return [self.registration_slot_mapping[reg_no] for reg_no in self.color_registration_mapping[color]]


if __name__ == "__main__":
    parking_lot = ParkingLot(6)
    print(parking_lot.available_parking_lots)

    car = Car("KA-01-HH-1234", "White")
    parking_lot.park_car(car)

    car = Car("KA-01-HH-9999", "White")
    parking_lot.park_car(car)

    car = Car("KA-01-BB-0001", "Black")
    parking_lot.park_car(car)

    car = Car("KA-01-HH-7777", "Red")
    parking_lot.park_car(car)

    car = Car("KA-01-HH-2701", "Blue")
    parking_lot.park_car(car)

    car = Car("KA-01-HH-3141", "Black")
    parking_lot.park_car(car)

    # When no slots are available then
    slot_no = parking_lot.get_nearest_slot()
    print(slot_no)
    slot_no = parking_lot.get_nearest_slot()
    print(slot_no)

    # Leave slot no 4
    slot_no_to_be_freed = 4
    parking_lot.free_slot(slot_no_to_be_freed)

    heapq.heappush(parking_lot.available_parking_lots, 4)

    car = Car("KA-01-P-333", "White")
    parking_lot.park_car(car)

    car = Car("DL-12-AA-9999", "White")
    parking_lot.park_car(car)
    parking_lot.status()
    print(parking_lot.available_parking_lots)
    print(parking_lot.registration_slot_mapping)
    print(parking_lot.color_registration_mapping)

    registration_numbers = parking_lot.get_registration_nos_by_color('White')
    print("White : {}".format(registration_numbers))
    registration_numbers = parking_lot.get_registration_nos_by_color('Red')
    print("Red : {}".format(registration_numbers))
    registration_numbers = parking_lot.get_registration_nos_by_color('Black')
    print("Black : {}".format(registration_numbers))

    slot_nos = parking_lot.get_slot_numbers_by_color('White')
    print("White : {}".format(slot_nos))
    slot_nos = parking_lot.get_slot_numbers_by_color('Red')
    print("Red : {}".format(slot_nos))
    slot_nos = parking_lot.get_slot_numbers_by_color('Black')
    print("Black : {}".format(slot_nos))
    parking_lot.status()
    parking_lot.free_slot(1)
    parking_lot.free_slot(2)
    parking_lot.free_slot(3)
    parking_lot.status()
\$\endgroup\$
2
  • 3
    \$\begingroup\$ I quite like this. It is straightforward and meets the requirements. One particularly nice thing about your code is how you index your data at the point of insertion (e.g. mapping registration numbers and colours). Too many designs nowadays use the brute force approach whereby they bunch all the data together then rely on expensive queries to get to the information; in your design, the information is readily available, and it does not even cost much more to store it in this way. \$\endgroup\$ – RWRkeSBZ Aug 1 '19 at 17:55
  • 1
    \$\begingroup\$ I find changing parking_lot.park_car(car) to parking_lot.park(car) to be nice. \$\endgroup\$ – Abdur-Rahmaan Janhangeer Aug 3 '19 at 5:23
3
\$\begingroup\$

This seems horribly overcomplex, and you don't provide a reason why.

A data structure that associates an increasing integer value (starting at 1) with an object of some type would be a list (an array in other languages). Normally they start at zero, but you can fix that pretty quickly by stuffing a dummy value in the l[0] slot.

Making that change would touch just about every part of your ParkingLot class, so I'll ignore the rest of it.

Given how simple the Car class is, I suggest you replace it with a collections.namedtuple instead.

\$\endgroup\$
1
  • \$\begingroup\$ Actually this was requirement from some client. and they wanted parking slots to be human readable like staring from 1. If I start from 0 then I have make changes at all retrieval levels. \$\endgroup\$ – Girish Gupta Aug 2 '19 at 4:23
0
\$\begingroup\$

I have divided this program into multiple files as per below:

|____ __init__.py
|____ parking_lot.py
|____ test.py
|____ process_input.py
|____ commands.txt

parking_lot.py is my main module.

#parking_lot.py
import heapq
from collections import defaultdict, OrderedDict


class Car:
    def __init__(self, registration_number, color):
        self.registration_number = registration_number
        self.color = color

    def __str__(self):
        return "Car [registration_number=" + self.registration_number + ", color=" + self.color + "]"


class ParkingLot:
    def __init__(self):
        self.registration_slot_mapping = dict()
        self.color_registration_mapping = defaultdict(list)
        # we need to maintain the orders of cars while showing 'status'
        self.slot_car_mapping = OrderedDict()
        # initialize all slots as free
        self.available_parking_lots = []

    def create_parking_lot(self, total_slots):
        # Using min heap as this will always give minimum slot number in O(1) time
        print("Created a parking lot with {} slots".format(total_slots))
        for i in range(1, total_slots + 1):
            heapq.heappush(self.available_parking_lots, i)
        return True

    def status(self):
        print("Slot No.  Registration No  Colour")
        for slot, car in self.slot_car_mapping.items():
            print("{}         {}    {}".format(slot, car.registration_number, car.color))
        return True

    def get_nearest_slot(self):
        return heapq.heappop(self.available_parking_lots) if self.available_parking_lots else None

    def leave(self, slot_to_be_freed):
        found = None
        for registration_no, slot in self.registration_slot_mapping.items():
            if slot == slot_to_be_freed:
                found = registration_no

        # Cleanup from all cache
        if found:
            heapq.heappush(self.available_parking_lots, slot_to_be_freed)
            del self.registration_slot_mapping[found]
            car_to_leave = self.slot_car_mapping[slot_to_be_freed]
            self.color_registration_mapping[car_to_leave.color].remove(found)
            del self.slot_car_mapping[slot_to_be_freed]
            print("Slot number {} is free".format(slot_to_be_freed))
            return True

        else:
            print("slot is not in use")
            return False

    def park(self, car):
        slot_no = self.get_nearest_slot()
        if slot_no is None:
            print("Sorry, parking lot is full")
            return
        print("Allocated slot number: {}".format(slot_no))
        self.slot_car_mapping[slot_no] = car
        self.registration_slot_mapping[car.registration_number] = slot_no
        self.color_registration_mapping[car.color].append(car.registration_number)
        return slot_no

    # Registration numbers of all cars of a particular colour
    def registration_numbers_for_cars_with_colour(self, color):
        registration_numbers = self.color_registration_mapping[color]
        print(", ".join(registration_numbers))
        return self.color_registration_mapping[color]

    # Slot numbers of all slots where a car of a particular colour is parked
    def slot_numbers_for_cars_with_colour(self, color):
        registration_numbers = self.color_registration_mapping[color]
        slots = [self.registration_slot_mapping[reg_no] for reg_no in registration_numbers]
        print(", ".join(map(str, slots)))
        return slots

    def slot_number_for_registration_number(self, registration_number):
        slot_number = None
        if registration_number in self.registration_slot_mapping:
            slot_number = self.registration_slot_mapping[registration_number]
            print(slot_number)
            return slot_number
        else:
            print("Not found")
            return slot_number

this test.py covers different test scenarios.

from parking_lot import ParkingLot, Car

parking_lot = ParkingLot()

cars = [
    Car('KA-01-HH-1234', 'White'),
    Car('KA-01-HH-9999', 'White'),
    Car('KA-01-BB-0001', 'Black'),
    Car('KA-01-HH-7777', 'Red'),
    Car('KA-01-HH-2701', 'Blue'),
    Car('KA-01-HH-3141', 'Black'),
]

assert parking_lot.create_parking_lot(6) is True

for i in range(0, len(cars)):
    assert parking_lot.park(cars[i]) == i + 1

assert parking_lot.leave(4) is True
assert parking_lot.status() is True

assert len(parking_lot.available_parking_lots) == 1
assert parking_lot.park(Car('KA-01-P-333', 'White')) == 4

assert parking_lot.registration_numbers_for_cars_with_colour('White') == ['KA-01-HH-1234', 'KA-01-HH-9999',
                                                                          'KA-01-P-333']
assert parking_lot.slot_numbers_for_cars_with_colour('White') == [1, 2, 4]
assert parking_lot.slot_number_for_registration_number('KA-01-HH-3141') == 6
assert parking_lot.slot_number_for_registration_number('MH-04-AY-1111') is None

How to run test cases:

python3.6 test.py

I also have added interactive command line utils where user can either pass a file as input or individual commands.

#process_input.py
#!/usr/bin/env python
import fileinput
import sys

from parking_lot import ParkingLot, Car

parking_lot = ParkingLot()


def process(command_params):
    command_with_params = command_params.strip().split(' ')
    # print(command_with_params)
    command = command_with_params[0]

    if command == 'create_parking_lot':
        assert len(command_with_params) == 2, "create_parking_lot needs no of slots as well"
        assert command_with_params[1].isdigit() is True, "param should be 'integer type'"
        parking_lot.create_parking_lot(int(command_with_params[1]))

    elif command == 'park':
        assert len(command_with_params) == 3, "park needs registration number and color as well"
        car = Car(command_with_params[1], command_with_params[2])
        parking_lot.park(car)

    elif command == 'leave':
        assert len(command_with_params) == 2, "leave needs slot number as well"
        assert command_with_params[1].isdigit() is True, "slot number should be 'integer type'"

        parking_lot.leave(int(command_with_params[1]))
    elif command == 'status':
        parking_lot.status()

    elif command == 'registration_numbers_for_cars_with_colour':
        assert len(command_with_params) == 2, "registration_numbers_for_cars_with_colour needs color as well"
        parking_lot.registration_numbers_for_cars_with_colour(command_with_params[1])

    elif command == 'slot_numbers_for_cars_with_colour':
        assert len(command_with_params) == 2, "slot_numbers_for_cars_with_colour needs color as well"
        parking_lot.slot_numbers_for_cars_with_colour(command_with_params[1])

    elif command == 'slot_number_for_registration_number':
        assert len(command_with_params) == 2, "slot_number_for_registration_number needs registration_number as well"
        parking_lot.slot_number_for_registration_number(command_with_params[1])

    elif command == 'exit':
        exit(0)
    else:
        raise Exception("Wrong command")


if len(sys.argv) == 1:
    while True:
        line = input()
        process(line)

else:
    for line in fileinput.input():
        process(line)

this commands.txt:

create_parking_lot 6
park KA-01-HH-1234 White
park KA-01-HH-9999 White
park KA-01-BB-0001 Black
park KA-01-HH-7777 Red
park KA-01-HH-2701 Blue
park KA-01-HH-3141 Black
leave 4
status
park KA-01-P-333 White
park DL-12-AA-9999 White
registration_numbers_for_cars_with_colour White
slot_numbers_for_cars_with_colour White
slot_number_for_registration_number KA-01-HH-3141
slot_number_for_registration_number MH-04-AY-1111

how to run through commands.txt as input file:

python3.6 process_input.py commands.txt

output:

Created a parking lot with 6 slots
Allocated slot number: 1
Allocated slot number: 2
Allocated slot number: 3
Allocated slot number: 4
Allocated slot number: 5
Allocated slot number: 6
Slot number 4 is free
Slot No.  Registration No  Colour
1         KA-01-HH-1234    White
2         KA-01-HH-9999    White
3         KA-01-BB-0001    Black
5         KA-01-HH-2701    Blue
6         KA-01-HH-3141    Black
Allocated slot number: 4
Sorry, parking lot is full
KA-01-HH-1234, KA-01-HH-9999, KA-01-P-333
1, 2, 4
6
Not found

Similarly you can run individual commands as :

python3.6 process_input.py 
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.