Code · LLD machine coding · PhonePe prep

Parking Lot

Design a multi-floor parking lot.

11 classes 253 lines Strategy View on GitHub ↗

Question

Design a multi-floor parking lot.

  • Floors have spots of different sizes. Vehicles have a size and a licence number.
  • Park: find a suitable free spot and issue a ticket. Unpark: free the spot and charge a fee for the time parked.
  • The spot-finding rule and the fee rule must be swappable.

Design: singleton ParkingLot, Strategy for spot selection (BestFitStrategy) and fees (FlatRateFeeStrategy), active tickets in a ConcurrentHashMap.

ParkingLot.java 61 lines GitHub ↗
package com.practice.phonepe.questions.parkinglot;


import com.practice.phonepe.questions.parkinglot.entities.ParkingFloor;
import com.practice.phonepe.questions.parkinglot.entities.ParkingSpot;
import com.practice.phonepe.questions.parkinglot.entities.ParkingTicket;
import com.practice.phonepe.questions.parkinglot.strategy.fee.FeeStrategy;
import com.practice.phonepe.questions.parkinglot.strategy.fee.FlatRateFeeStrategy;
import com.practice.phonepe.questions.parkinglot.strategy.parking.BestFitStrategy;
import com.practice.phonepe.questions.parkinglot.strategy.parking.ParkingStrategy;
import com.practice.phonepe.questions.parkinglot.vehicle.Vehicle;

import java.util.*;
import java.util.concurrent.ConcurrentHashMap;

public class ParkingLot {
    private static ParkingLot instance;
    private final List<ParkingFloor> floors = new ArrayList<>();
    private final Map<String, ParkingTicket> activeTickets;
    private FeeStrategy feeStrategy;
    private ParkingStrategy parkingStrategy;

    private ParkingLot() {
        this.feeStrategy = new FlatRateFeeStrategy();
        this.parkingStrategy = new BestFitStrategy();
        this.activeTickets = new ConcurrentHashMap<>();
    }

    public static synchronized ParkingLot getInstance() {
        if (instance == null) {
            instance = new ParkingLot();
        }
        return instance;
    }

    public void addFloor(ParkingFloor floor) {
        floors.add(floor);
    }

    public void setFeeStrategy (FeeStrategy feeStrategy) {
        this.feeStrategy = feeStrategy;
    }

    public void setParkingStrategy(ParkingStrategy parkingStrategy) {
        this.parkingStrategy = parkingStrategy;
    }

    public Optional<ParkingTicket> parkVehicle(Vehicle vehicle) {
        Optional<ParkingSpot> availableSpot = parkingStrategy.findSpot(floors, vehicle);

        if (availableSpot.isPresent()) {
            ParkingSpot spot = availableSpot.get();
            ParkingTicket ticket = new ParkingTicket(spot, vehicle);
            activeTickets.put(vehicle.getLicenceNumber(), ticket);
            System.out.printf("%s parked at %s. Ticket: %s\n", vehicle.getLicenceNumber(), spot.getSpotId(), ticket.getTicketId());
            return Optional.of(ticket);
        }

        System.out.println("No available spot for " + vehicle.getLicenceNumber());
        return Optional.empty();
    }

    public Optional<Double> unparkVehicle(String licenseNumber) {
        ParkingTicket ticket = activeTickets.remove(licenseNumber);
        if (ticket == null) {
            System.out.println("Ticket not found");
            return Optional.empty();
        }

        ticket.setExistTimeStamp();
        ticket.getParkingSpot().unparkVehicle();

        Double parkingFee = feeStrategy.calculateFee(ticket);

        return Optional.of(parkingFee);
    }
}
ParkingLotTest.java 4 lines GitHub ↗
package com.practice.phonepe.questions.parkinglot;

public class ParkingLotTest
{
}
entities/ParkingFloor.java 17 lines GitHub ↗
package com.practice.phonepe.questions.parkinglot.entities;

import java.util.ArrayList;
import java.util.List;

public class ParkingFloor {
    private final int floorNumber;
    private List<ParkingSpot> spot;

    public ParkingFloor(int floorNumber) {
        this.floorNumber = floorNumber;
        this.spot = new ArrayList<>();
    }

    public void addSpot(ParkingSpot parkingSpot) {
        spot.add(parkingSpot);
    }

    public List<ParkingSpot> getSpots() {
        return spot;
    }

}
entities/ParkingSpot.java 64 lines GitHub ↗
package com.practice.phonepe.questions.parkinglot.entities;

import com.practice.phonepe.questions.parkinglot.vehicle.Vehicle;
import com.practice.phonepe.questions.parkinglot.vehicle.VehicleSize;

import static com.practice.phonepe.questions.parkinglot.vehicle.VehicleSize.*;

public class ParkingSpot {
    private final int spotId;
    private Boolean isOccupied;
    private Vehicle parkedVehicle;
    private final VehicleSize spotSize;

    public ParkingSpot(int spotId, VehicleSize spotSize) {
        this.spotId = spotId;
        this.spotSize = spotSize;
        this.isOccupied = false;
        this.parkedVehicle = null;
    }

    public int getSpotId() {
        return spotId;
    }

    public Boolean getOccupied() {
        return isOccupied;
    }

    public Vehicle getParkedVehicle() {
        return parkedVehicle;
    }

    public void setParkedVehicle(Vehicle parkedVehicle) {
        this.parkedVehicle = parkedVehicle;
    }

    public VehicleSize getSpotSize() {
        return spotSize;
    }

    public synchronized Boolean isAvailable() {
        return !isOccupied;
    }

    public synchronized void parkVehicle(Vehicle vehicle) {
        this.parkedVehicle = vehicle;
        this.isOccupied = true;
    }

    public synchronized void unparkVehicle() {
        this.parkedVehicle = null;
        this.isOccupied = false;
    }

    public Boolean canFitVehicle(Vehicle vehicle) {
        if(isOccupied) {
            return false;
        }
        switch (vehicle.getSize()) {
            case SMALL:
                return SMALL.equals(getSpotSize());
            case MEDIUM:
                return MEDIUM.equals(getSpotSize()) || LARGE.equals(getSpotSize());
            case LARGE:
                return LARGE.equals(getSpotSize());

            default:
                return false;
        }
    }

    public synchronized boolean tryPark(Vehicle vehicle) {
        if (isOccupied || !canFitVehicle(vehicle)) {
            return false;
        }
        parkVehicle(vehicle);
        return true;
    }

}
entities/ParkingTicket.java 35 lines GitHub ↗
package com.practice.phonepe.questions.parkinglot.entities;

import com.practice.phonepe.questions.parkinglot.vehicle.Vehicle;

import java.util.Date;
import java.util.UUID;

public class ParkingTicket {
    private final String ticketId;
    private final ParkingSpot parkingSpot;
    private final Vehicle vehicle;
    private final long entryTimeStamp;
    private long existTimeStamp;

    public ParkingTicket(ParkingSpot parkingSpot, Vehicle vehicle) {
        this.ticketId = UUID.randomUUID().toString();
        this.parkingSpot = parkingSpot;
        this.vehicle = vehicle;
        this.entryTimeStamp = new Date().getTime();
    }

    public String getTicketId() {
        return ticketId;
    }

    public ParkingSpot getParkingSpot() {
        return parkingSpot;
    }

    public Vehicle getVehicle() {
        return vehicle;
    }

    public long getEntryTimeStamp() {
        return entryTimeStamp;
    }

    public long getExistTimeStamp() {
        return existTimeStamp;
    }

    public void setExistTimeStamp() {
        this.existTimeStamp = new Date().getTime();
    }
}
strategy/fee/FeeStrategy.java 5 lines GitHub ↗
package com.practice.phonepe.questions.parkinglot.strategy.fee;

import com.practice.phonepe.questions.parkinglot.entities.ParkingTicket;

public interface FeeStrategy {

    double calculateFee(ParkingTicket parkingTicket);
}
strategy/fee/FlatRateFeeStrategy.java 11 lines GitHub ↗
package com.practice.phonepe.questions.parkinglot.strategy.fee;

import com.practice.phonepe.questions.parkinglot.entities.ParkingTicket;

public class FlatRateFeeStrategy implements FeeStrategy {

    private static final double rate_per_hour = 10.0;
    @Override
    public double calculateFee(ParkingTicket parkingTicket) {

        long duration = parkingTicket.getExistTimeStamp() - parkingTicket.getEntryTimeStamp();
        long hour = (duration / (1000*60*60)) + 1;
        return hour*rate_per_hour;

    }
}
strategy/parking/BestFitStrategy.java 28 lines GitHub ↗
package com.practice.phonepe.questions.parkinglot.strategy.parking;

import com.practice.phonepe.questions.parkinglot.entities.ParkingFloor;
import com.practice.phonepe.questions.parkinglot.entities.ParkingSpot;
import com.practice.phonepe.questions.parkinglot.vehicle.Vehicle;

import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.Optional;

public class BestFitStrategy implements ParkingStrategy {

    @Override
    public Optional<ParkingSpot> findSpot(List<ParkingFloor> floors, Vehicle vehicle) {
        List<ParkingSpot> candidates = new ArrayList<>();

        for (ParkingFloor floor : floors) {
            for (ParkingSpot spot : floor.getSpots()) {
                if (spot.canFitVehicle(vehicle)) {
                    candidates.add(spot);
                }
            }
        }

        candidates.sort(Comparator.comparingInt(spot -> spot.getSpotSize().ordinal()));

        for (ParkingSpot spot : candidates) {
            if (spot.tryPark(vehicle)) {
                return Optional.of(spot);
            }
        }

        return Optional.empty();
    }
}
strategy/parking/ParkingStrategy.java 9 lines GitHub ↗
package com.practice.phonepe.questions.parkinglot.strategy.parking;

import com.practice.phonepe.questions.parkinglot.entities.ParkingFloor;
import com.practice.phonepe.questions.parkinglot.entities.ParkingSpot;
import com.practice.phonepe.questions.parkinglot.vehicle.Vehicle;

import java.util.List;
import java.util.Optional;

public interface ParkingStrategy {
    Optional<ParkingSpot> findSpot(List<ParkingFloor> floors, Vehicle vehicle);
}
vehicle/Vehicle.java 15 lines GitHub ↗
package com.practice.phonepe.questions.parkinglot.vehicle;

public class Vehicle {

    private final VehicleSize size;
    private final String licenceNumber;

    public Vehicle(VehicleSize size, String licenceNumber) {
        this.size = size;
        this.licenceNumber = licenceNumber;
    }

    public VehicleSize getSize() {
        return size;
    }

    public String getLicenceNumber() {
        return licenceNumber;
    }
}
vehicle/VehicleSize.java 4 lines GitHub ↗
package com.practice.phonepe.questions.parkinglot.vehicle;

public enum VehicleSize {
    SMALL, MEDIUM, LARGE
}