Code · LLD machine coding · PhonePe prep
Parking Lot
Design a multi-floor parking lot.
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
}