Machine Coding Problem

Ride Pooling Logic

maco60macoAllgeoroute-overlap-algorithms
Commonly Asked By:UberLyftGrabOla

Requirements & System Scope

Functional Scope (In-Scope)

  • Detour-Aware Path Matching: Find compatible rides for new requests by evaluating detour impacts on existing riders.
  • Pickup-Before-Dropoff Constraints: Enforce logical ordering, ensuring a rider's pickup always happens before their dropoff.
  • Optimal Stop Insertion: Enumerate stop list insertion combinations to select paths that minimize total detour distance.
  • Segment-Based Fare Splitting: Calculate individual fare cuts by prorating distance segments across co-riders using the Strategy Pattern.

Explicit Boundaries (Out-of-Scope)

  • No Real-World Map Engines (OSM/Google Maps): Excludes live traffic updates, turn-by-turn navigation data, or OpenStreetMap integrations; uses simplified 2D Cartesian spatial coordinates.
  • No Passenger Driver Churn Predictors: Does not predict driver cancellations or rider churn patterns.

Class Diagram & Entity Relationships

Structural layout showing relationships between pooled routes, stop stops, and insertion builders:

Loading...
  • Route Lists: PooledRoute holds lists of stops and tracks vehicle capacity limits.
  • Route Steps: Stop encapsulates stop types (PICKUP/DROPOFF), locations, and rider IDs.

Design Patterns & SOLID Principles

  • Strategy Pattern (Fare Splits): Decoupling fare splitting algorithms (Segment split, Distance Proportional) into separate strategies allows pricing changes without affecting routing models.
  • Single Responsibility Principle (SRP):RideRequest holds user details; Stop manages location states; PooledRoute coordinates path insertions.

Core Execution Workflows

Rider Insertion Workflow

  1. Rider submits a pooling request: tryInsertRider(riderId, pickup, dropoff).
  2. Enumerate all possible insertion indices where the pickup precedes the dropoff.
  3. For each permutation, run a simulated insertion:
    1. Check capacity: verify that passenger count at any stop does not exceed capacity bounds.
    2. Calculate detours: check if the new route exceeds the detour limits of any existing passengers.
  4. Select the permutation that yields the shortest total detour distance.
  5. If a valid configuration is found, commit the stop updates; otherwise, reject the pooling request.

Concurrency & Thread Safety Strategy

When multiple riders look for pooling matches simultaneously:

  • Isolated Thread Simulation: Perform detour simulations on local copies of route arrays to avoid thread conflicts.
  • Synchronized Route Commit Locks: Lock final route changes to ensure that driver assignments remain consistent and correct.

Complete Clean Code Blueprint

Clean reference designs demonstrating optimal detour stop insertions in Java and Python:

// ─── JAVA BLUEPRINT ──────────────────────────────────────────────────────────
import java.util.*;
import java.util.concurrent.*;
import java.util.concurrent.locks.*;

enum StopType { PICKUP, DROPOFF }

class Location {
    final double x;
    final double y;

    public Location(double x, double y) {
        this.x = x;
        this.y = y;
    }

    public double distanceTo(Location other) {
        return Math.sqrt(Math.pow(this.x - other.x, 2) + Math.pow(this.y - other.y, 2));
    }
}

class Stop {
    private final StopType type;
    private final String riderId;
    private final Location location;

    public Stop(StopType type, String riderId, Location location) {
        this.type = type;
        this.riderId = riderId;
        this.location = location;
    }

    public StopType getType() { return type; }
    public String getRiderId() { return riderId; }
    public Location getLocation() { return location; }
}

class RideRequest {
    private final String riderId;
    private final Location pickup;
    private final Location dropoff;
    private final double maxDetourFactor; // e.g., 1.5 means max distance is 1.5 * direct distance

    public RideRequest(String riderId, Location pickup, Location dropoff, double maxDetourFactor) {
        this.riderId = riderId;
        this.pickup = pickup;
        this.dropoff = dropoff;
        this.maxDetourFactor = maxDetourFactor;
    }

    public String getRiderId() { return riderId; }
    public Location getPickup() { return pickup; }
    public Location getDropoff() { return dropoff; }
    public double getMaxDetourFactor() { return maxDetourFactor; }

    public double getDirectDistance() {
        return pickup.distanceTo(dropoff);
    }
}

// Strategy Pattern for Fare Splitting
interface FareSplittingStrategy {
    Map<String, Double> calculateFareSplits(PooledRoute route, double baseRatePerKm);
}

class SegmentBasedFareSplitStrategy implements FareSplittingStrategy {
    @Override
    public Map<String, Double> calculateFareSplits(PooledRoute route, double baseRatePerKm) {
        List<Stop> stops = route.getStops();
        Map<String, Double> finalFares = new HashMap<>();
        if (stops.size() < 2) return finalFares;

        Set<String> activeRiders = new HashSet<>();
        Location currentPos = stops.get(0).getLocation();

        for (int i = 0; i < stops.size(); i++) {
            Stop stop = stops.get(i);
            
            if (i > 0) {
                Location nextPos = stop.getLocation();
                double segmentDist = currentPos.distanceTo(nextPos);
                double segmentCost = segmentDist * baseRatePerKm;

                if (!activeRiders.isEmpty()) {
                    double splitCost = segmentCost / activeRiders.size();
                    for (String rider : activeRiders) {
                        finalFares.put(rider, finalFares.getOrDefault(rider, 0.0) + splitCost);
                    }
                }
                currentPos = nextPos;
            }

            if (stop.getType() == StopType.PICKUP) {
                activeRiders.add(stop.getRiderId());
            } else {
                activeRiders.remove(stop.getRiderId());
            }
        }
        return finalFares;
    }
}

class PooledRoute {
    private final String driverId;
    private final int capacity;
    private final List<Stop> stops = new ArrayList<>();
    private final Map<String, RideRequest> riderRequests = new HashMap<>();

    public PooledRoute(String driverId, int capacity, Location startLocation) {
        this.driverId = driverId;
        this.capacity = capacity;
        // Seed driver start location stop
        this.stops.add(new Stop(StopType.PICKUP, "DRIVER", startLocation));
    }

    public String getDriverId() { return driverId; }
    public int getCapacity() { return capacity; }
    public List<Stop> getStops() { return stops; }

    public double calculateTotalDistance() {
        return getRouteDistance(stops);
    }

    public boolean tryInsertRider(RideRequest req) {
        int bestPickupIdx = -1;
        int bestDropoffIdx = -1;
        double minDistance = Double.MAX_VALUE;

        // Iterate over all insertion points
        for (int i = 1; i <= stops.size(); i++) {
            for (int j = i; j <= stops.size(); j++) {
                List<Stop> testStops = new ArrayList<>(stops);
                testStops.add(i, new Stop(StopType.PICKUP, req.getRiderId(), req.getPickup()));
                testStops.add(j + 1, new Stop(StopType.DROPOFF, req.getRiderId(), req.getDropoff()));

                if (isValidRoute(testStops, req)) {
                    double dist = getRouteDistance(testStops);
                    if (dist < minDistance) {
                        minDistance = dist;
                        bestPickupIdx = i;
                        bestDropoffIdx = j;
                    }
                }
            }
        }

        if (bestPickupIdx != -1) {
            stops.add(bestPickupIdx, new Stop(StopType.PICKUP, req.getRiderId(), req.getPickup()));
            stops.add(bestDropoffIdx + 1, new Stop(StopType.DROPOFF, req.getRiderId(), req.getDropoff()));
            riderRequests.put(req.getRiderId(), req);
            return true;
        }
        return false;
    }

    private boolean isValidRoute(List<Stop> routeStops, RideRequest newReq) {
        int currentRiders = 0;
        // Verify capacity is never violated
        for (Stop stop : routeStops) {
            if (stop.getType() == StopType.PICKUP && !stop.getRiderId().equals("DRIVER")) {
                currentRiders++;
            } else if (stop.getType() == StopType.DROPOFF) {
                currentRiders--;
            }
            if (currentRiders > capacity) return false;
        }

        // Verify detour constraint for all riders including the new one
        Map<String, Double> actualDistances = calculateTravelDistances(routeStops);
        
        // 1. Check existing riders
        for (Map.Entry<String, RideRequest> entry : riderRequests.entrySet()) {
            double actual = actualDistances.getOrDefault(entry.getKey(), 0.0);
            double limit = entry.getValue().getDirectDistance() * entry.getValue().getMaxDetourFactor();
            if (actual > limit) return false;
        }

        // 2. Check the candidate rider
        double candidateActual = actualDistances.getOrDefault(newReq.getRiderId(), 0.0);
        double candidateLimit = newReq.getDirectDistance() * newReq.getMaxDetourFactor();
        return candidateActual <= candidateLimit;
    }

    private Map<String, Double> calculateTravelDistances(List<Stop> routeStops) {
        Map<String, Double> distanceMap = new HashMap<>();
        Map<String, Double> startAccumulated = new HashMap<>();
        
        double currentTotal = 0.0;
        Location prev = routeStops.get(0).getLocation();

        for (Stop stop : routeStops) {
            currentTotal += prev.distanceTo(stop.getLocation());
            prev = stop.getLocation();

            if (stop.getType() == StopType.PICKUP && !stop.getRiderId().equals("DRIVER")) {
                startAccumulated.put(stop.getRiderId(), currentTotal);
            } else if (stop.getType() == StopType.DROPOFF) {
                double pickupTotal = startAccumulated.getOrDefault(stop.getRiderId(), 0.0);
                distanceMap.put(stop.getRiderId(), currentTotal - pickupTotal);
            }
        }
        return distanceMap;
    }

    private double getRouteDistance(List<Stop> routeStops) {
        if (routeStops.size() < 2) return 0.0;
        double distance = 0.0;
        for (int i = 0; i < routeStops.size() - 1; i++) {
            distance += routeStops.get(i).getLocation().distanceTo(routeStops.get(i + 1).getLocation());
        }
        return distance;
    }
}

class RideMatchingManager {
    private final List<PooledRoute> activeRoutes = new CopyOnWriteArrayList<>();
    private final ReentrantLock lock = new ReentrantLock();

    public void addRoute(PooledRoute route) {
        activeRoutes.add(route);
    }

    public boolean matchRider(RideRequest request) {
        lock.lock();
        try {
            for (PooledRoute route : activeRoutes) {
                if (route.tryInsertRider(request)) {
                    System.out.println("Matched Rider " + request.getRiderId() + " to Driver " + route.getDriverId());
                    return true;
                }
            }
            return false;
        } finally {
            lock.unlock();
        }
    }

    public List<PooledRoute> getActiveRoutes() {
        return activeRoutes;
    }
}

// Complete Simulation
public class Main {
    public static void main(String[] args) {
        RideMatchingManager manager = new RideMatchingManager();

        // 1. Create a route representing a driver from origin (0, 0)
        PooledRoute route = new PooledRoute("D-101", 3, new Location(0.0, 0.0));
        manager.addRoute(route);

        // 2. Submit concurrent or sequential ride requests
        System.out.println("--- Submitting ride request 1 (R-1) ---");
        RideRequest req1 = new RideRequest("R-1", new Location(1.0, 1.0), new Location(5.0, 5.0), 1.5);
        boolean success1 = manager.matchRider(req1);
        System.out.println("Match success: " + success1);

        System.out.println("\n--- Submitting ride request 2 (R-2) with close overlap ---");
        RideRequest req2 = new RideRequest("R-2", new Location(2.0, 1.5), new Location(4.5, 4.0), 1.4);
        boolean success2 = manager.matchRider(req2);
        System.out.println("Match success: " + success2);

        // 3. Compute final segment splits
        System.out.println("\n--- Calculating Fair splits for overlapping segments ---");
        FareSplittingStrategy splitStrategy = new SegmentBasedFareSplitStrategy();
        Map<String, Double> splits = splitStrategy.calculateFareSplits(route, 2.5); // $2.5 per unit distance
        for (Map.Entry<String, Double> entry : splits.entrySet()) {
            System.out.println("Rider " + entry.getKey() + " Fare: $" + String.format("%.2f", entry.getValue()));
        }
    }
}

💬Review

Help Us Improve

How helpful was this walkthrough?

Click a star to rate. We actively use this feedback to refine and update our system design content.

Placeholder
Optional but highly appreciated!

Discussion

Share your thoughts, ask questions, or help others.

Loading comments...