Conversation
| @@ -0,0 +1,103 @@ | |||
| # 実装前ノート | |||
There was a problem hiding this comment.
この問題を見たとき、同じ key に対する value は、 timestamp でソートした二分探索木に入れるのがよいと思いました。また、 get() では二分探索木の中で、 timestamp_prev <= timestamp となる最大の timestamp_prev を探すとき、 O(log n) で探索できると思います。
Python で二分探索木の実装、挑戦してみます…?
There was a problem hiding this comment.
二分探索木を実装したことがなかったので step4 として挑戦してみます!
質問なのですが、 ソートした配列を用いるか、二分探索木を用いるかどちらか選べる状況ではどのような点を考慮すべきでしょうか。
いまのところ、二分探索木を用いると、計算量において挿入・削除が平均 O(log n) で実行できる点で有利で、実装において (標準ライブラリで提供されていないこともあり) 複雑度が増すという理解です。
There was a problem hiding this comment.
あくまで自分の場合にになりますが、こんな感じで考えています。
最初にどれくらいの時間で処理を終わらせるべきか、どれくらいのメモリ使用量で処理すべきかを求めます。これらはプログラムに対する要求で決まってくると思います。続いて、いくつかの実装方針について、時間計算量・空間計算量を求めます。次に、時間計算量・空間計算量の式にデータサイズを代入し、計算ステップ数を概算します。そのあと、計算ステップ数から実行時間・仕様メモリ量を概算します。これらのうち、要求を満たすものの中で、最も実装が簡単なものを選びます。
なお、処理時間が重要な場合には、自分なら C++ で書くことを選びます。また、 C++ には標準ライブラリに std::map があり、今回のような処理が比較的楽に書けます。
理想的には、実装したい処理に応じて、言語を書き分けられるようになるとよいと思います。
There was a problem hiding this comment.
くわしい説明をありがとうございます。計算ステップに換算して考えると n が大きくないときや定数倍の評価がしやすくなると思いました。時間計算量とその係数からなんとなくで実時間を見積もっていたので、ステップ数に換算して考えるようにしてみます。
ステップ数と実時間の対応についてのログも discord 上を漁ってみます。
There was a problem hiding this comment.
言語の選択は頭に入っていませんでした。 python が提供しているデータ構造も把握しきれていないので覚えていこうと思います。
(ログや他の方の解答から、max_heap、平衡木がないことを把握しました。)
| @@ -0,0 +1,29 @@ | |||
| from collections import defaultdict | |||
| class TimeMap: | |||
|
|
|||
There was a problem hiding this comment.
ここ開けないほうが普通かと思ったんですが、開ける流儀もあるんですかね。
https://peps.python.org/pep-0008/#blank-lines
There was a problem hiding this comment.
class 宣言直後の空行について、cpython/Lib/ 内でも空行を入れる、入れないが混在していて、それぞれの基準はよくわかりませんでした。。。小さいクラスであれば間延びして見えるのでわざわざ空行を入れることもないですね。
以下、cpython/Lib で見つけた空行があるクラス定義の例です 。
空行があるもの:
https://github.com/python/cpython/blob/5c58e728b1391c258b224fc6d88f62f42c725026/Lib/__future__.py#L81
https://github.com/python/cpython/blob/5c58e728b1391c258b224fc6d88f62f42c725026/Lib/doctest.py#L2457
ファイル内で空行の有無が混在しているもの:
https://github.com/python/cpython/blob/5c58e728b1391c258b224fc6d88f62f42c725026/Lib/calendar.py#L94
上の例から、クラスが大きいことのシグナルとして空行を入れるかとも思ったのですが、あてはまらないものもありました。
https://github.com/python/cpython/blob/5c58e728b1391c258b224fc6d88f62f42c725026/Lib/plistlib.py#L464
There was a problem hiding this comment.
ありがとうございます。いや、私も CPython をさらっと見て両方あるなあと思ったんですよ。
Google style guide も開けろと明示的には言っていないですね。例は空いてないです。docstring がある場合には空けるようですが。
https://google.github.io/styleguide/pyguide.html#35-blank-lines
There was a problem hiding this comment.
リンクありがとうございます。Google style guid の例を確認しました。
cpython のコードを見てみて、表現の違いに意味を見出だそうとして結局よくわからず負荷がかかったので、一貫していることの大切さを理解できました。
| 設計の選択:binary search tree を tree として実装するか、array で実装するか | ||
| - array による binary search tree の実装:parent のインデックスを `i` として、左の子を インデックス `2i + 1`, 右の子を インデックス `2i + 2` の要素とすればいい。 | ||
| array で実装する場合の計算コストの見積もり: | ||
| 今回は、配列に 10^14 要素分の領域が必要になる。(1 <= timestamp <= 10^7 であり、かつ、key となる timestamp の値は増加するいっぽうなので 「要素数 = 深さ」となる。空間計算量として最悪の状況で配列はスカスカ) |
There was a problem hiding this comment.
At most 2 * 10^5 calls will be made to set and get.
とありますね。
配列での表現はcomplete binary treeの時に使うと思います。
There was a problem hiding this comment.
1 <= timestamp <= 10^7 から最大でそれだけの深さになり得ると早合点していました。
要素数の上限は set が呼ばれる回数ですね。。。
|
|
||
| 計算量の見積もり: | ||
| - set(), get() | ||
| - いずれの操作も木がバランスされているとき O(log_n)、木が偏っているとき O(n) |
There was a problem hiding this comment.
There was a problem hiding this comment.
リンクありがとうございます。AVL-tree と red-black tree の名前を聞いたことがあるぐらいで、バランスの操作が大変そうで実装したことはありませんでした。(他に関連知識といえば、ファイルシステムとかデータベースは B-tree というのを使っているらしい、ぐらいの知識です。) 平衡木はほかにもいろいろあるんですね。
ひとまず AVL-tree で insert と search を実装してみようと思います。
| # とても大きなテストケース(10^5ぐらい)をコピペすると、コピペに時間がかかる。 | ||
| 1. vim で開いたテスト用のファイルにテストケースをペーストしたところ、ペーストがしばらく終わらなかった。 | ||
| 2. ファイルを開いている vim のプロセスを、`kill -9 PID` で kill したところ、プロンプトに切り替わり、プロンプトへのペーストが継続した。 | ||
| 3. `ps -x | grep <vimで開いていたファイル名>` でペーストを継続しているプロセスを探してみたが見つけられなかった。 |
There was a problem hiding this comment.
vim だと paste mode にしてから貼るとかいるんじゃないでしょうか。他、cat でどうでしょう。
% cat > /tmp/a.txt
test
test
^D
% cat /tmp/a.txt
test
test
There was a problem hiding this comment.
paste mode の存在を初めて知りました。コマンド例までありがとうございます。
試してみたところ、paste mode、cat コマンドのいずれも、システムのクリップボードからのペーストで十秒単位の時間がかかるのは変わらずでした。どうやらターミナルへの描画がボトルネックになっていそうでした。
ターミナルに大量にペーストすると遅くなるしくみは思ったより複雑そうなテーマだったので追加で色々調べてみようと思います。
現時点での理解としては、ターミナル上での入力は1文字ごとに画面に反映されてほしいので出力をバッファリングしておらず、そのため、大量の文字一度にペーストするとペーストが終わるまで1文字ずつ画面に出力し続けてしまうので遅い、という理解です。
https://stackoverflow.com/questions/3857052/why-is-printing-to-stdout-so-slow-can-it-be-sped-up
leetcode だとあまり意識することはありませんが、入出力がボトルネックになることもあるという実例ですね。
言語: Python
問題: https://leetcode.com/problems/time-based-key-value-store/
znote.md には bisect モジュール関連の調査と、データ構造の設計問題で考慮すべきと思われることを書きました。