辞書と二分探索で時刻つきの設定の履歴を設計する

設定の​名前ごとに、​いつどの​値に​したかを​全部残し、​「ある​時刻には​どの​値だったか」を​答える​仕組みを​作る。​時刻が​増える​順に​書き足される​ことを​使い、​読むときは​二分探索で​探します。

海外で定番の問題リスト 4 本のうち 2 本に入っています (NeetCode 150、Grind 75)

問題

機械の設定の履歴を覚える仕組みを、クラス TimeStore として作ってください。設定の名前 key ごとに、ある時刻にどの値へ変えたかを残しておき、あとから「その時刻にはどの値だったか」を答えます。次の 2 つの操作を持ちます。

  • set(key, value, timestamp) は、時刻 timestamp に key の値を value にしたことを覚えます。前の値は消さずに残します
  • get(key, timestamp) は、key について、時刻が timestamp 以下の set のうち最も新しいものの値を返します。そういう set が無ければ、空の文字列 "" を返します

入力は、呼ぶ操作の名前の並び ops と、それぞれの引数の並び args で渡します。ops の最初はクラスの名前で、args の最初は空の並びです。出力は操作ごとの戻り値の並びで、作った所と set は null です。

  • set に渡す時刻は、どの key かに関わらず、呼ぶたびに必ず前より大きくなります
  • get に渡す時刻は自由で、前より小さくなることもあります
  • key と value は英小文字と数字の 1 文字以上 100 文字以下、時刻は 1 以上 10000000 以下です
  • 操作の回数は 200000 回以下です

入力と出力の例

例 1

時刻 3 に eco、時刻 8 に fast にしています。時刻 6 に聞くと、まだ eco の頃です。

入力

ops = ["TimeStore", "set",
  "get", "get", "set", "get",
  "get"]
args = [[], ["mode", "eco", 3],
  ["mode", 2], ["mode", 5],
  ["mode", "fast", 8],
  ["mode", 8], ["mode", 6]]

出力

[null, null, "", "eco", null,
 "fast", "eco"]

例 2

設定の名前ごとに、別々の履歴を持ちます。

入力

ops = ["TimeStore", "set",
  "set", "set", "get", "get",
  "get", "get"]
args = [[], ["temp", "20", 1],
  ["fan", "low", 4],
  ["temp", "24", 7],
  ["temp", 6], ["fan", 3],
  ["fan", 100], ["temp", 7]]

出力

[null, null, null, null, "20",
 "", "low", "24"]

例 3

一度も set していない名前と、最初の set より前の時刻は、どちらも空の文字列です。

入力

ops = ["TimeStore", "get",
  "set", "get", "get"]
args = [[], ["door", 5],
  ["door", "open", 10],
  ["door", 9], ["door", 10]]

出力

[null, "", null, "", "open"]

面接での聞かれ方

テックリードの面接では、操作の形を渡したあと、データの持ち方と前提の崩れ方を聞かれます。

  • 「時刻つきのキーと値の置き場を作ってください。ある時刻での値を引けるように」
  • 「set の時刻が増えていく決まりが無かったらどうしますか」と、前提を外したときの持ち方を聞かれます
  • 「履歴がどんどん増えますが、記憶はどうしますか」「古い履歴を消すならどう決めますか」と、運用まで続くこともあります
  • 「書き込みが多い場合と読み出しが多い場合で、持ち方を変えますか」と、取り引きを話させられます

時刻が増える順に来るのか、同じ時刻の set があり得るのか、見つからないときに何を返すのかを、書き始める前に確かめます。この問題の速さは「時刻が増える順に来る」前提に支えられているので、そこを口に出して確かめること自体が評価されます。

アルゴリズムのチートシートの「ハッシュマップ」に、​この​型の​要点を​表と​コードで​まとめています。

main.py
class TimeStore:
    def __init__(self):
        # ここに持つ物を書きます
        pass

    def set(self, key, val, ts):
        # ここに書きます
        pass

    def get(self, key, ts):
        # ここに書きます
        pass


# 「実行」を押すと、下の行が動きます
obj = TimeStore()
print(obj.set("mode", "eco", 3))

できているか「例で確かめる」を押すと、問題の例を全部この関数に渡して、通ったかをここに出します