Solution: Design Underground System
Tags: algorithms, javascript, java, python
Published: 2021-03-20 11:21:28

*This is part of a series of Leetcode solution explanations ([index](https://dev.to/seanpgallivan/leetcode-solutions-index-57fl)). If you liked this solution or found it useful,* ***please like*** *this post and/or* ***upvote*** *[my solution post on Leetcode's forums](https://leetcode.com/problems/design-underground-system/discuss/1118296).*

---

#### [Leetcode Problem #1396 (*Medium*): Design Underground System](https://leetcode.com/problems/design-underground-system/)

---

#### ***Description:***

<br />(*Jump to*: [*Solution Idea*](#idea) || *Code*: [*JavaScript*](#javascript-code) | [*Python*](#python-code) | [*Java*](#java-code) | [*C++*](#c-code))

>
Implement the UndergroundSystem class:
  -  `void checkIn(int id, string stationName, int t)`
      -  A customer with a card id equal to `id`, gets in the station `stationName` at time `t`.
      -  A customer can only be checked into one place at a time.
  -  `void checkOut(int id, string stationName, int t)`
      -  A customer with a card id equal to `id`, gets out from the station `stationName` at time `t`.
  -  `double getAverageTime(string startStation, string endStation)`
      -  Returns the average time to travel between the `startStation` and the `endStation`.
      -  The average time is computed from all the previous traveling from `startStation` to `endStation` that happened directly.
      -  Call to `getAverageTime` is always valid.
>
You can assume all calls to `checkIn` and `checkOut` methods are consistent. If a customer gets in at time `t1` at some station, they get out at time `t2` with `t2 > t1`. All events happen in chronological order.

---

#### ***Examples:***

Example 1:||
|---:|---|
Input:|example

>
Example 1:||
|---:|---|
Input|["UndergroundSystem", "checkIn", "checkIn", "checkIn", "checkOut", "checkOut", "checkOut", "getAverageTime", "getAverageTime", "checkIn", "getAverageTime", "checkOut", "getAverageTime"]<br>[[], [45,"Leyton",3], [32,"Paradise",8], [27,"Leyton",10], [45,"Waterloo",15], [27,"Waterloo",20], [32,"Cambridge",22], ["Paradise","Cambridge"], ["Leyton","Waterloo"], [10,"Leyton",24], ["Leyton","Waterloo"], [10,"Waterloo",38], ["Leyton","Waterloo"]]
Output|[null, null, null, null, null, null, null, 14.00000, 11.00000, null, 11.00000, null, 12.00000]
Explanation|UndergroundSystem undergroundSystem = new UndergroundSystem();<br>undergroundSystem.checkIn(45, "Leyton", 3);<br>undergroundSystem.checkIn(32, "Paradise", 8);<br>undergroundSystem.checkIn(27, "Leyton", 10);<br>undergroundSystem.checkOut(45, "Waterloo", 15);<br>undergroundSystem.checkOut(27, "Waterloo", 20);<br>undergroundSystem.checkOut(32, "Cambridge", 22);<br>undergroundSystem.getAverageTime("Paradise", "Cambridge");       // return 14.00000. There was only one travel from "Paradise" (at time 8) to "Cambridge" (at time 22)<br>undergroundSystem.getAverageTime("Leyton", "Waterloo");          // return 11.00000. There were two travels from "Leyton" to "Waterloo", a customer with id=45 from time=3 to time=15 and a customer with id=27 from time=10 to time=20. So the average time is ( (15-3) + (20-10) ) / 2 = 11.00000<br>undergroundSystem.checkIn(10, "Leyton", 24);<br>undergroundSystem.getAverageTime("Leyton", "Waterloo");          // return 11.00000<br>undergroundSystem.checkOut(10, "Waterloo", 38);<br>undergroundSystem.getAverageTime("Leyton", "Waterloo");          // return 12.00000
>
Example 2:||
|---:|---|
Input|["UndergroundSystem", "checkIn", "checkOut", "getAverageTime", "checkIn", "checkOut", "getAverageTime", "checkIn", "checkOut", "getAverageTime"]<br>[[], [10,"Leyton",3], [10,"Paradise",8], ["Leyton","Paradise"], [5,"Leyton",10], [5,"Paradise",16], ["Leyton","Paradise"], [2,"Leyton",21], [2,"Paradise",30], ["Leyton","Paradise"]]
Output|[null, null, null, 5.00000, null, null, 5.50000, null, null, 6.66667]
Explanation|UndergroundSystem undergroundSystem = new UndergroundSystem();<br>undergroundSystem.checkIn(10, "Leyton", 3);<br>undergroundSystem.checkOut(10, "Paradise", 8);<br>undergroundSystem.getAverageTime("Leyton", "Paradise"); // return 5.00000<br>undergroundSystem.checkIn(5, "Leyton", 10);<br>undergroundSystem.checkOut(5, "Paradise", 16);<br>undergroundSystem.getAverageTime("Leyton", "Paradise"); // return 5.50000<br>undergroundSystem.checkIn(2, "Leyton", 21);<br>undergroundSystem.checkOut(2, "Paradise", 30);<br>undergroundSystem.getAverageTime("Leyton", "Paradise"); // return 6.66667

---

#### ***Constraints:***
>
 - There will be at most `20000` operations.
 - `1 <= id, t <= 10^6`
 - All strings consist of uppercase and lowercase English letters, and digits.
 - `1 <= stationName.length <= 10`
 - Answers within `10^-5` of the actual value will be accepted as correct.

---

#### ***Idea:***

<br />(*Jump to*: [*Problem Description*](#description) || *Code*: [*JavaScript*](#javascript-code) | [*Python*](#python-code) | [*Java*](#java-code) | [*C++*](#c-code))

The first thing we should realize is that since checkins and checkouts are separated, we'll need some type of data structure in which to store checkin information until we find the matching checkout information.

The next important realization is that since we only ultimately care about route times, we don't really need to store checkout info at all. As long as we store the checkin info until we get to the checkout info, we can actually just then store the trip info separately by route and get rid of the checkin and checkout information. This will help keep the space needed to a minimum.

As we'll want to look up checkin and route info by id and route name, we should use **Map** structures for both (**checkins** & **routes**). For the route information, we'll only need to keep track of the number of trips and the total duration, so that we can calculate the average as needed. We can also use a concatenated name for the key in the route map in order to store the trip as a whole, rather than having to keep track of both ends separately.

---

#### ***Implementation:***

For better efficiency, Javascript can use a Uint32Array for route, Python can use a tuple for checkins, and Java and C++ can use Pairs for checkins and Java can use Pairs instead of concatenating the station names.

---

#### ***Javascript Code:***
<br />(*Jump to*: [*Problem Description*](#description) || [*Solution Idea*](#idea))
```javascript
class UndergroundSystem {
    constructor() {
        this.checkins = new Map()
        this.routes = new Map()
    }
    checkIn(id, stationName, t) {
        this.checkins.set(id, [stationName, t])
    };
    checkOut(id, stationName, t) {
        let [stn, start] = this.checkins.get(id),
            route = stn + "," + stationName
        this.checkins.delete(id)
        if (!this.routes.has(route))
            this.routes.set(route, new Uint32Array(2))
        let trip = this.routes.get(route)
        trip[0]++, trip[1] += t - start
    };
    getAverageTime(startStation, endStation) {
        let [count, sum] = this.routes.get(startStation + "," + endStation)
        return sum / count
    };
};
```

---

#### ***Python Code:***
<br />(*Jump to*: [*Problem Description*](#description) || [*Solution Idea*](#idea))
```python
class UndergroundSystem:
    def __init__(self):
        self.checkins = defaultdict()
        self.routes = defaultdict()
    def checkIn(self, id: int, stationName: str, t: int) -> None:
        self.checkins[id] = (stationName, t)
    def checkOut(self, id: int, stationName: str, t: int) -> None:
        stn, start = self.checkins[id]
        del self.checkins[id]
        route = stn + "," + stationName
        if route not in self.routes: self.routes[route] = [0,0]
        trip = self.routes[route]
        trip[0] += 1
        trip[1] += t - start
    def getAverageTime(self, startStation: str, endStation: str) -> float:
        count, rsum = self.routes[startStation + "," + endStation]
        return rsum / count
```

---

#### ***Java Code:***
<br />(*Jump to*: [*Problem Description*](#description) || [*Solution Idea*](#idea))
```java
class UndergroundSystem {
    Map<Integer, Pair<String, Integer>> checkins = new HashMap<>();
    Map<Pair<String, String>, int[]> routes = new HashMap<>();
    public void checkIn(int id, String stationName, int t) {
        checkins.put(id, new Pair(stationName, t));
    }
    public void checkOut(int id, String stationName, int t) {
        Pair<String, Integer> cIn = checkins.get(id);
        checkins.remove(id);
        Pair<String, String> route = new Pair(cIn.getKey(), stationName);
        int[] trip = routes.getOrDefault(route, new int[2]);
        trip[0]++;
        trip[1] += t - cIn.getValue();
        routes.put(route, trip);
    }
    public double getAverageTime(String startStation, String endStation) {
        int[] trip = routes.get(new Pair(startStation, endStation));
        return (double)trip[1] / trip[0];
    }
}
```

---

#### ***C++ Code:***
<br />(*Jump to*: [*Problem Description*](#description) || [*Solution Idea*](#idea))
```c++
class UndergroundSystem {
public:
    unordered_map<int, pair<string, int>> checkins;
    unordered_map<string, pair<int, int>> routes;
    void checkIn(int id, string stationName, int t) {
        checkins[id] = {stationName, t};
    }
    void checkOut(int id, string stationName, int t) {
        auto [stn, start] = checkins[id];
        checkins.erase(id);
        string route = stn + "," + stationName;
        routes[route].first++, routes[route].second += t - start;
    }
    double getAverageTime(string startStation, string endStation) {
        auto& [count, sum] = routes[startStation + "," + endStation];
        return (double)sum / count;
    }
};
```