Calls pointer walks the calls · return value underneath
keyStore one list per key, oldest first: value in the box, timestamp underneath
class TimeMap { private static class Version { String value; int t; Version(String value, int t) { this.value = value; this.t = t; } } private Map<String, List<Version>> keyStore; public TimeMap() { keyStore = new HashMap<>(); // key -> list of versions } public void set(String key, String value, int timestamp) { if (!keyStore.containsKey(key)) { keyStore.put(key, new ArrayList<>()); } keyStore.get(key).add(new Version(value, timestamp)); } public String get(String key, int timestamp) { String res = ""; List<Version> values = keyStore.getOrDefault(key, new ArrayList<>()); int l = 0; int r = values.size() - 1; while (l <= r) { int m = l + (r - l) / 2; if (values.get(m).t <= timestamp) { res = values.get(m).value; // candidate; look later l = m + 1; } else { r = m - 1; } } return res; }}
- call
- —
- l, r
- —
- m
- —
- res
- —
- returns
- —
Why it works
- Brute force scans the key's whole history on every
getand keeps the latest timestamp that is not after the query: O(n) per call. - Key insight: the lists are already sorted. Set timestamps only increase, so appending keeps each key's list in time order for free.
getwants the last entry witht ≤ timestamp: an "is it too late?" test that reads no, no, …, no, yes, yes. Binary search for the boundary, keeping the best "no" seen inres. - O(1) set, O(log n) get, where n is the number of versions of that key. Space is O(total sets).
- Pitfall: stopping at an exact match is not enough. Most queries fall between two timestamps. On
t ≤ timestamp, record the value and keep searching to the right; the loop ends on the last such version, exact match or not. A query earlier than every set, or an unknown key, returns"". - In Java, a
TreeMap<Integer, String>per key answers the same query withfloorEntry(timestamp)in O(log n). It also copes with set times that arrive out of order, at O(log n) per set instead of O(1).