Skip to content

Commit 83b04a8

Browse files
committed
feat: add implementation and documentation for LeetCode 2621 'Sleep'
1 parent e643781 commit 83b04a8

3 files changed

Lines changed: 2224 additions & 0 deletions

File tree

Lines changed: 363 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,363 @@
1+
# Sleep - 非同期スリープ関数の実装
2+
3+
## 目次
4+
5+
- [概要](#overview)
6+
- [アルゴリズム要点(TL;DR)](#tldr)
7+
- [図解](#figures)
8+
- [正しさのスケッチ](#correctness)
9+
- [計算量](#complexity)
10+
- [Python実装](#impl)
11+
- [CPython最適化ポイント](#cpython)
12+
- [エッジケースと検証観点](#edgecases)
13+
- [FAQ](#faq)
14+
15+
---
16+
17+
<h2 id="overview">概要</h2>
18+
19+
### 問題要約
20+
21+
正の整数 `millis` を受け取り、その時間(ミリ秒)だけ非同期にスリープする関数を実装する。
22+
23+
### 要件
24+
25+
- 入力: `1 <= millis <= 1000` の正の整数
26+
- 出力: `millis` ミリ秒後に完了する awaitable オブジェクト(任意の値を返してよい)
27+
- 実際のスリープ時間が `millis` から若干ずれても許容される
28+
29+
### 制約
30+
31+
- 外部ライブラリは標準ライブラリのみ(`asyncio`
32+
- 非同期処理の基本的な理解が必要
33+
34+
---
35+
36+
<h2 id="tldr">アルゴリズム要点(TL;DR)</h2>
37+
38+
- **戦略**: `asyncio.sleep()` を使用するか、イベントループの `call_later` で Future を解決
39+
- **データ構造**: Future または Task(asyncio の非同期プリミティブ)
40+
- **時間計算量**: O(1) - 定数時間での処理開始
41+
- **空間計算量**: O(1) - Future/Task オブジェクト1つのみ
42+
- **メモリ**: 最小限(非同期タスク管理のオーバーヘッドのみ)
43+
44+
---
45+
46+
<h2 id="figures">図解</h2>
47+
48+
### フローチャート
49+
50+
```mermaid
51+
flowchart TD
52+
Start[Start sleep] --> Validate{Check millis valid}
53+
Validate -- Invalid --> Error[Raise ValueError]
54+
Validate -- Valid --> Convert[Convert millis to seconds]
55+
Convert --> CreateFuture[Create Future object]
56+
CreateFuture --> Schedule[Schedule callback via call_later]
57+
Schedule --> Await[Await Future completion]
58+
Await --> Callback[Callback sets result]
59+
Callback --> Resume[Coroutine resumes]
60+
Resume --> End[Return None]
61+
```
62+
63+
**説明**: 入力検証後、ミリ秒を秒に変換し、イベントループに遅延コールバックをスケジュール。コールバックが Future を解決すると、await が再開される。
64+
65+
### データフロー図
66+
67+
```mermaid
68+
graph LR
69+
subgraph Input_Layer
70+
A[millis int] --> B[Validate range]
71+
end
72+
subgraph Async_Layer
73+
B --> C[Convert to seconds]
74+
C --> D[Get event loop]
75+
D --> E[Create Future]
76+
E --> F[Schedule call_later]
77+
end
78+
subgraph Execution_Layer
79+
F --> G[Event loop waits]
80+
G --> H[Callback fires]
81+
H --> I[Future resolved]
82+
end
83+
I --> J[Return to caller]
84+
```
85+
86+
**説明**: 入力値を検証・変換し、非同期レイヤーで Future を作成してイベントループにスケジュール。実行時にコールバックが発火して Future が解決される。
87+
88+
---
89+
90+
<h2 id="correctness">正しさのスケッチ</h2>
91+
92+
### 不変条件
93+
94+
- `millis` は常に正の整数(1 以上 1000 以下)
95+
- Future は必ず1回だけ解決される(`set_result` は1回のみ呼ばれる)
96+
- イベントループは正しく動作している(標準の asyncio 前提)
97+
98+
### 網羅性
99+
100+
- 入力範囲内のすべての `millis` 値に対して正しく動作
101+
- イベントループがない場合のエラーハンドリング(実装による)
102+
103+
### 基底条件
104+
105+
- `millis` の最小値(1ミリ秒)でも正しく動作
106+
- Future が即座に解決されるケースも含む
107+
108+
### 終了性
109+
110+
- `call_later` は必ず指定時間後にコールバックを呼び出す
111+
- コールバックは Future を解決し、`await` が必ず復帰する
112+
- イベントループが停止していない限り、必ず終了する
113+
114+
---
115+
116+
<h2 id="complexity">計算量</h2>
117+
118+
### 時間計算量
119+
120+
- **O(1)**: Future の作成とコールバックのスケジューリングは定数時間
121+
- **実際の待機時間**: O(millis) だが、これは計算量ではなく実時間
122+
123+
### 空間計算量
124+
125+
- **O(1)**: Future オブジェクトとコールバッククロージャのみ
126+
- スタックフレームやヒープ使用量は最小限
127+
128+
### アプローチ比較
129+
130+
| アプローチ | 実装難易度 | メモリ | コード量 | 推奨度 |
131+
| -------------------------- | ---------- | ------ | -------- | ------------- |
132+
| `asyncio.sleep()` 直接使用 || O(1) | 1行 | ★★★ |
133+
| `call_later` + Future || O(1) | 5行 | ★★ |
134+
| busy wait(ポーリング) || O(1) | 3行 | ✗(CPU 100%) |
135+
136+
**推奨**: 問題の趣旨が「自分で実装」なら `call_later` + Future、実務なら `asyncio.sleep()` を使用。
137+
138+
---
139+
140+
<h2 id="impl">Python実装</h2>
141+
142+
### アプローチ1: asyncio.sleep() を使用(最もシンプル)
143+
144+
```python
145+
from __future__ import annotations
146+
import asyncio
147+
148+
async def sleep(millis: int) -> None:
149+
"""
150+
指定されたミリ秒数だけ非同期にスリープする
151+
152+
Args:
153+
millis: スリープするミリ秒数(1-1000)
154+
155+
Returns:
156+
None
157+
158+
Time: O(1) for scheduling, Space: O(1)
159+
"""
160+
# 入力検証(制約に基づく)
161+
if not isinstance(millis, int) or millis < 1 or millis > 1000:
162+
raise ValueError("millis must be an integer between 1 and 1000")
163+
164+
# ミリ秒を秒に変換してスリープ
165+
await asyncio.sleep(millis / 1000)
166+
```
167+
168+
### アプローチ2: call_later + Future(低レベル実装)
169+
170+
```python
171+
from __future__ import annotations
172+
import asyncio
173+
from typing import Any
174+
175+
async def sleep(millis: int) -> None:
176+
"""
177+
イベントループの call_later を使用した低レベル実装
178+
179+
Args:
180+
millis: スリープするミリ秒数(1-1000)
181+
182+
Returns:
183+
None
184+
185+
Time: O(1), Space: O(1)
186+
"""
187+
# 入力検証
188+
if not isinstance(millis, int) or millis < 1 or millis > 1000:
189+
raise ValueError("millis must be an integer between 1 and 1000")
190+
191+
# イベントループ取得
192+
loop = asyncio.get_event_loop()
193+
194+
# Future 作成(コルーチンが待機するオブジェクト)
195+
future: asyncio.Future[None] = loop.create_future()
196+
197+
# コールバックをスケジュール(millis ミリ秒後に Future を解決)
198+
loop.call_later(
199+
millis / 1000, # 秒単位に変換
200+
future.set_result, # Future を解決するコールバック
201+
None # 解決時の値(任意)
202+
)
203+
204+
# Future が解決されるまで待機
205+
await future
206+
```
207+
208+
### LeetCode形式での提出コード(最小実装)
209+
210+
```python
211+
import asyncio
212+
213+
async def sleep(millis: int) -> None:
214+
await asyncio.sleep(millis / 1000)
215+
216+
# 使用例
217+
# let t = Date.now()
218+
# sleep(100).then(() => console.log(Date.now() - t)) # 100
219+
```
220+
221+
Python での等価な使用例:
222+
223+
```python
224+
import asyncio
225+
import time
226+
227+
async def main():
228+
t = time.time()
229+
await sleep(100)
230+
print(int((time.time() - t) * 1000)) # ~100
231+
232+
# 実行
233+
asyncio.run(main())
234+
```
235+
236+
---
237+
238+
<h2 id="cpython">CPython最適化ポイント</h2>
239+
240+
### 最適化1: 直接 `asyncio.sleep()` を使用
241+
242+
- CPython の `asyncio` は C 拡張で最適化されている
243+
- 独自実装よりも高速で安定
244+
245+
### 最適化2: 型チェックの省略(制約が保証される場合)
246+
247+
```python
248+
async def sleep(millis: int) -> None:
249+
# LeetCode環境では入力が保証されているため検証不要
250+
await asyncio.sleep(millis / 1000)
251+
```
252+
253+
### 最適化3: 除算の事前計算(大量呼び出し時)
254+
255+
```python
256+
MILLIS_TO_SECONDS = 0.001
257+
258+
async def sleep(millis: int) -> None:
259+
await asyncio.sleep(millis * MILLIS_TO_SECONDS) # 除算より乗算が高速
260+
```
261+
262+
### パフォーマンスノート
263+
264+
- `asyncio.sleep()` の精度はOSのタイマー精度に依存(通常1-15ミリ秒)
265+
- 極端に短いスリープ(1-10ミリ秒)では誤差が大きくなる可能性がある
266+
- イベントループのオーバーヘッドは無視できるレベル(マイクロ秒単位)
267+
268+
---
269+
270+
<h2 id="edgecases">エッジケースと検証観点</h2>
271+
272+
### エッジケース一覧
273+
274+
1. **最小値**: `millis = 1`
275+
- 1ミリ秒のスリープが正しく動作するか
276+
- OS のタイマー精度による誤差に注意
277+
278+
2. **最大値**: `millis = 1000`
279+
- 1秒のスリープが正しく完了するか
280+
281+
3. **境界値前後**:
282+
- `millis = 0` → ValueError(範囲外)
283+
- `millis = 1001` → ValueError(範囲外)
284+
285+
4. **型エラー**:
286+
- `millis = 100.5` → 型エラー(整数のみ)
287+
- `millis = "100"` → 型エラー
288+
289+
5. **並行実行**:
290+
291+
```python
292+
async def test_concurrent():
293+
await asyncio.gather(
294+
sleep(100),
295+
sleep(200),
296+
sleep(150)
297+
)
298+
```
299+
300+
- 複数の sleep が同時に正しく動作するか
301+
302+
### 検証観点
303+
304+
- **精度**: 実際のスリープ時間が `millis` に近いか(±10%以内が目安)
305+
- **非ブロッキング**: 他の非同期タスクをブロックしないか
306+
- **リソースリーク**: Future が適切に解放されるか
307+
- **イベントループ依存**: 異なるイベントループで動作するか
308+
309+
---
310+
311+
<h2 id="faq">FAQ</h2>
312+
313+
### Q1: なぜ `asyncio.sleep()` を使わず自分で実装する必要があるのか?
314+
315+
**A**: この問題は非同期プログラミングの理解を深めるための教育的な意図があります。実務では `asyncio.sleep()` を使用すべきです。
316+
317+
### Q2: `time.sleep()` ではダメなのか?
318+
319+
**A**: `time.sleep()` はブロッキング関数で、イベントループ全体を停止させます。非同期処理では必ず `asyncio.sleep()` や await 可能な実装を使用してください。
320+
321+
```python
322+
# ❌ ダメな例(イベントループをブロック)
323+
import time
324+
async def bad_sleep(millis: int) -> None:
325+
time.sleep(millis / 1000) # 他のタスクも全て停止
326+
327+
# ✅ 正しい例
328+
async def good_sleep(millis: int) -> None:
329+
await asyncio.sleep(millis / 1000) # 他のタスクは継続
330+
```
331+
332+
### Q3: ミリ秒の精度は保証されるか?
333+
334+
**A**: いいえ。OS のタイマー精度、イベントループの負荷、CPython のスケジューリングによって誤差が生じます。問題文でも「minor deviation」が許容されています。
335+
336+
### Q4: `call_later``asyncio.sleep()` の違いは?
337+
338+
**A**:
339+
340+
- `call_later`: 低レベルAPI。コールバックベース。
341+
- `asyncio.sleep()`: 高レベルAPI。内部で `call_later` を使用。await 可能。
342+
343+
実装は等価ですが、`asyncio.sleep()` の方がシンプルで可読性が高いです。
344+
345+
### Q5: TypeScript の `setTimeout` と Python の実装の違いは?
346+
347+
**A**:
348+
349+
```typescript
350+
// TypeScript
351+
async function sleep(millis: number): Promise<void> {
352+
return new Promise(resolve => setTimeout(resolve, millis));
353+
}
354+
355+
// Python 等価実装
356+
async def sleep(millis: int) -> None:
357+
loop = asyncio.get_event_loop()
358+
future = loop.create_future()
359+
loop.call_later(millis / 1000, future.set_result, None)
360+
await future
361+
```
362+
363+
両者は概念的に同じですが、Python は秒単位、TypeScript はミリ秒単位である点に注意。

0 commit comments

Comments
 (0)