回答A: OpenAI GPT-5 mini
回答:
LRU キャッシュの実装(ハッシュマップ + 双方向連結リストを使用)
- ハッシュマップ(dict)はキー -> ノードへの O(1) アクセスを提供します
- 双方向連結リストは使用順序を維持します: 最も最近使われたものが head の直後、最も古く使われたものが tail の直前
- キーがアクセスまたは更新されると、そのノードを head に移動します
- 容量を超えた場合、tail のノード (LRU) を削除し、そのキーをマップから削除します
class _Node:
"""キーと値を保持する双方向連結リストのノード。"""
def init(self, key=None, value=None):
self.key = key
self.value = value
self.prev = None
self.next...
全文を表示 ▼
回答:
LRU キャッシュの実装(ハッシュマップ + 双方向連結リストを使用)
- ハッシュマップ(dict)はキー -> ノードへの O(1) アクセスを提供します
- 双方向連結リストは使用順序を維持します: 最も最近使われたものが head の直後、最も古く使われたものが tail の直前
- キーがアクセスまたは更新されると、そのノードを head に移動します
- 容量を超えた場合、tail のノード (LRU) を削除し、そのキーをマップから削除します
class _Node:
"""キーと値を保持する双方向連結リストのノード。"""
def init(self, key=None, value=None):
self.key = key
self.value = value
self.prev = None
self.next = None
class LRUCache:
def init(self, capacity: int):
"""指定された容量で LRUCache を初期化します。
容量が 0 以下の場合は、実質的にキャッシュを使用不可に(get は -1 を返し、put は何もしない)とします。
"""
self.capacity = max(0, int(capacity))
self.cache = {} # key -> _Node
# エッジチェックを避けるための head と tail のセントネルノード
self.head = _Node() # Most recently used comes right after head
self.tail = _Node() # Least recently used is right before tail
self.head.next = self.tail
self.tail.prev = self.head
self.size = 0
# 内部ヘルパー: ノードを head の直後に追加(最近使用されたことを示す)
def _add_node(self, node: _Node) -> None:
node.prev = self.head
node.next = self.head.next
self.head.next.prev = node
self.head.next = node
# 内部ヘルパー: 連結リストから既存のノードを削除
def _remove_node(self, node: _Node) -> None:
prev = node.prev
nxt = node.next
if prev:
prev.next = nxt
if nxt:
nxt.prev = prev
node.prev = node.next = None
# 内部ヘルパー: 既存のノードを head に移動(最も最近使用された)
def _move_to_head(self, node: _Node) -> None:
self._remove_node(node)
self._add_node(node)
# 内部ヘルパー: 最も最近使われていないノード(tail の直前)を取り出す
def _pop_tail(self) -> _Node:
node = self.tail.prev
if node is self.head:
return None
self._remove_node(node)
return node
def get(self, key: int) -> int:
"""キーが存在する場合に関連する値を返します。存在しない場合は -1 を返します。
アクセスはそのキーを最近使用されたものとしてマークします。
平均時間 O(1)。
"""
if self.capacity == 0:
return -1
node = self.cache.get(key)
if not node:
return -1
# アクセスされたノードを head に移動(最近使用)
self._move_to_head(node)
return node.value
def put(self, key: int, value: int) -> None:
"""キーと値を挿入または更新します。容量を超えた場合は LRU を削除します。
平均時間 O(1)。
"""
if self.capacity == 0:
return
node = self.cache.get(key)
if node:
# 更新して最近使用としてマーク
node.value = value
self._move_to_head(node)
else:
# 新しいノードを挿入
new_node = _Node(key, value)
self.cache[key] = new_node
self._add_node(new_node)
self.size += 1
if self.size > self.capacity:
# 最も最近使われていないノードを削除
tail_node = self._pop_tail()
if tail_node:
del self.cache[tail_node.key]
self.size -= 1
与えられたテストシーケンスでの実装のデモンストレーション
if name == "main":
cache = LRUCache(2)
cache.put(1, 10)
cache.put(2, 20)
print(cache.get(1)) # 期待される出力: 10
cache.put(3, 30) # Evicts key 2
print(cache.get(2)) # 期待される出力: -1
cache.put(4, 40) # Evicts key 1
print(cache.get(1)) # 期待される出力: -1
print(cache.get(3)) # 期待される出力: 30
print(cache.get(4)) # 期待される出力: 40
判定
勝利票
3 / 3
平均スコア
総合点
総評
ハッシュマップと二重連結リストを用いたLRUキャッシュの実装として、全体的に強力でよく構成されています。コードは期待されるすべての出力(10, -1, -1, 30, 40)を正しく生成し、capacity 0 のようなエッジケースに対処し、リスト操作を単純化するために sentinel nodes を使用しており、全体にわたって明確なコメントが含まれています。実装はきれいで Python の慣習に従っており、get と put の両方で平均 O(1) を達成しています。小さな指摘として、get メソッドが 'if not node' を使用しており、理論的には node が偽値となる場合に問題になる可能性がありますが、ノードはオブジェクトであるため実際には問題ありません。capacity=1 のエッジケースも暗黙的に正しく扱われています。全体として、非常に小さなスタイル上の考慮点のみを残す高品質な回答です。
採点詳細を表示 ▼
正確さ
重み 35%実装は期待される5つの出力(10, -1, -1, 30, 40)を正しく生成します。二重連結リストの操作は正しく、sentinel nodes によりエッジケースのバグが防がれており、追い出し(eviction)ロジックも妥当です。get と put の両方が平均 O(1) です。capacity=0 のエッジケースも処理されています。get 内の 'if not node' チェックはノードがオブジェクトである限り技術的には問題ありませんが、'if node is None' のほうがより明示的でしょう。
完全性
重み 20%必要なすべての構成要素が含まれています: _Node クラス、get と put を持つ LRUCache クラス、リスト操作のための内部ヘルパー、sentinel nodes、capacity 0 および 1 のエッジケース処理、期待される出力コメント付きの完全なテストシーケンス、および main guard。重要な欠落はありません。
コード品質
重み 20%コードはきれいで整理されており、Pythonの慣習に従っています。コメントは全体のアプローチと各メソッドを説明しています。sentinel nodes の使用は良い設計選択です。ヘルパーメソッドは命名が適切で単一責務です。型ヒントも使用されています。小さな指摘として、'if not node' は 'if node is None' よりやや明示的でない点と、sentinel nodes が常に存在することを考えると _remove_node の prev/nxt に対する null チェックは不要ですが、これらは非常に小さな問題です。
実用性
重み 15%実装はそのまま実用的なシナリオで使えます。capacity 0 をうまく扱い、インターフェースは簡潔で、sentinel nodes を用いた設計により堅牢です。コードは自己完結しており実行可能です。スレッドセーフ化や TTL のような拡張も容易です。デモは期待される動作を明確に示しています。
指示遵守
重み 10%すべての指示に従っています: OrderedDict や functools.lru_cache は使用しておらず、hash map + doubly linked list を実装し、LRUCache という名前のクラスが正しいインターフェースを持ち、期待される出力コメント付きのテストシーケンスが含まれ、エッジケースにも対処しており、コメントがアプローチを説明しています。コードは完全で実行可能です。
総合点
総評
dict と番兵ノードを持つ二重連結リストを組み合わせた、正しく実行可能な LRUCache 実装を提供しています。get/put を平均 O(1) で実現し、示されたテストに対して期待される出力を生成します。コメントは明確で、capacity 0 の扱いも適切です。小さな問題点として、_pop_tail の戻り値注釈は _Node を示していますが None を返す可能性があり、また _remove_node にあるいくつかの防御的チェックは番兵の不変条件と重複しており必須ではありませんが、害はありません。
採点詳細を表示 ▼
正確さ
重み 35%LRU として正しい振る舞いです: get はノードを MRU 位置に移動し、put は既存キーを更新/移動し、オーバーフロー時には LRU を追い出します。提供されたテストシーケンスは期待通り 10, -1, -1, 30, 40 を出力します。capacity 0 は get が -1 を返し、put は無操作として扱われます。
完全性
重み 20%クラスの完全な実装、内部ヘルパー、そして要求された通りのテストシーケンスを含んでいます。コメントはアプローチと主要操作を説明しています。capacity 0 のエッジケースは明示的に扱われており、capacity 1 は追い出しロジックによって自然に機能します。
コード品質
重み 20%コードはクリーンで読みやすく、番兵ノードを用いてリスト操作を簡素化するなど構造が良好です。小さな品質上の指摘として、_pop_tail の戻り値注釈が None を返す可能性と一致しておらず、また _remove_node の一部のチェックは番兵による不変条件と重複していて冗長です。
実用性
重み 15%実用的な参照実装です: 操作は O(1)、禁止されたライブラリを避けており、適応も容易です。デモ用コードはそのまま実行可能です。
指示遵守
重み 10%すべての指示に従っています: OrderedDict や functools は使用せず、ハッシュマップ+二重連結リストを使用し、説明的なコメントを含み、(特に capacity 0 を含む)エッジケースに対処し、指定されたテストを含む完全に実行可能なコードを提供しています。
総合点
総評
これは教科書的な品質のLRUキャッシュ実装を提供する、卓越した回答です。コードは正確で効率的、そして非常に良く構成されており、二重連結リストのロジックを簡素化するためにセンチネルノードという高度な手法を用いています。すべての制約に完全に準拠し、優れたコメントが含まれ、エッジケースも丁寧に扱われ、必要な実行可能なテストケースも提供されています。本課題に対する模範解答と言えます。
採点詳細を表示 ▼
正確さ
重み 35%実装は完全に正しいです。`get`と`put`の両操作のロジックは最小使用順(LRU)を正しく維持し、容量制約にも従っています。提供されたテストシーケンスは期待どおりの出力を生成します。ハッシュマップと二重連結リストの組み合わせにより、両操作が平均O(1)の時間計算量を達成しています。容量が0などのエッジケースも正しく処理されています。
完全性
重み 20%提出物は完全に完了しています。`LRUCache`クラス、プライベートなヘルパー`_Node`クラス、そして要求どおりの完全に実行可能なデモブロック(`if __name__ == "__main__":`)が含まれています。プロンプトで要求されたすべての要素が提供されています。
コード品質
重み 20%コード品質は卓越しています。ノード操作のためのプライベートヘルパーメソッド(`_add_node`、`_remove_node`など)を使用しているため、主要な`get`や`put`メソッドは簡潔で読みやすくなっています。ヘッドとテールのセンチネルノードの実装は高度で効率的なパターンであり、リスト操作ロジック内でのエッジケースのチェックを不要にしています。コメントは明快で簡潔、実装の「何を」だけでなく「なぜ」を説明しており、プロフェッショナルな解答です。
実用性
重み 15%この回答は実用的かつ教育的価値が高いです。システム設計で広く使われる基本的なキャッシュアルゴリズムの、ゼロからの古典的な実装です。このコードはLRUキャッシュを理解または実装する必要がある人にとって優れた参考資料になり、非常に一般的なソフトウェアエンジニアリングの面接問題に対する代表的な解答です。
指示遵守
重み 10%回答はプロンプトの指示に細心の注意を払って従っています。ハッシュマップとカスタムの二重連結リストを用いて`LRUCache`クラスを正しく実装しており、禁止されていた`collections.OrderedDict`や`functools.lru_cache`を明示的に使用していません。コードは実行可能で、コメントが付され、指定されたテストケースが含まれています。