Conversation
| ```python | ||
| class Solution: | ||
| def maxProfit(self, prices: List[int]) -> int: | ||
| acc_min = float("inf") |
There was a problem hiding this comment.
こちらのコメントをご参照ください。
hemispherium/LeetCode_Arai60#10 (comment)
| diffs = [prices[i + 1] - prices[i] for i in range(len(prices) - 1)] | ||
| return self.__max_profit(diffs, 0, len(diffs)) | ||
|
|
||
| def __max_profit(self, diffs: List[int], begin: int, end: int) -> int: |
There was a problem hiding this comment.
アンダースコア 2 つから始まるメソッドは mangling の対象となり、内部用であるという意図を強く感じさせるものの、完全に private というわけではありません。通常はアンダースコア 1 つで十分だと思います。
参考までにスタイルガイドへのリンクを共有いたします。
https://peps.python.org/pep-0008/#method-names-and-instance-variables
To avoid name clashes with subclasses, use two leading underscores to invoke Python’s name mangling rules.
Python mangles these names with the class name: if class Foo has an attribute named __a, it cannot be accessed by Foo.__a. (An insistent user could still gain access by calling Foo._Foo__a.) Generally, double leading underscores should be used only to avoid name conflicts with attributes in classes designed to be subclassed.
https://google.github.io/styleguide/pyguide.html#3162-naming-conventions
Prepending a double underscore (__ aka “dunder”) to an instance variable or method effectively makes the variable or method private to its class (using name mangling); we discourage its use as it impacts readability and testability, and isn’t really private. Prefer a single underscore.
なお、このスタイルガイドは“唯一の正解”というわけではなく、数あるガイドラインの一つに過ぎません。チームによって重視される書き方や慣習も異なります。そのため、ご自身の中に基準を持ちつつも、最終的にはチームの一般的な書き方に合わせることをお勧めします。
There was a problem hiding this comment.
コメントと参考の提示ありがとうございます.
技術的な部分は理解しました.そういわれるとダンダーを使うことのメリットがそれらを上回るとは言えないというところで納得したので,今後はアンダースコア1つを通常の選択として運用したいと思います.
There was a problem hiding this comment.
細かい点で恐縮ですが、ダンダーはメソッド名の前後にアンダースコアが2つ付いているメソッドを指すようです。
https://docs.python.org/ja/3.14/library/dataclasses.html#module-contents
訳注:dunderはdouble underscoreの略で、メソッド名の前後にアンダースコアが2つ付いているメソッド
There was a problem hiding this comment.
補足ありがとうございます。自分も気になっていたので検索していたところでした。
| <summary> 分割統治による $O(N \log N)$ 時間アルゴリズム </summary> | ||
|
|
||
| - [アルゴリズムイントロダクション](https://www.amazon.co.jp/%E3%82%A2%E3%83%AB%E3%82%B4%E3%83%AA%E3%82%BA%E3%83%A0%E3%82%A4%E3%83%B3%E3%83%88%E3%83%AD%E3%83%80%E3%82%AF%E3%82%B7%E3%83%A7%E3%83%B3-%E7%AC%AC3%E7%89%88-%E7%B7%8F%E5%90%88%E7%89%88-%E4%B8%96%E7%95%8C%E6%A8%99%E6%BA%96MIT%E6%95%99%E7%A7%91%E6%9B%B8-%E3%82%B3%E3%83%AB%E3%83%A1%E3%83%B3/dp/476490408X), CLRS, の分割統治の章で見たことある. | ||
| - 階差を作って部分配列の和の最大化問題に言い換え→"真ん中"をまたぐ部分配列なら線形で最適解を計算出来て,またがない奴は部分問題の解になるから,(またぐやつ, またがない左右の部分問題) の3つに分割して統治する,という方針だったはず. |
There was a problem hiding this comment.
私も初めてみました。
この問題を見て最大部分配列問題として捉えるのは視点の幅がすごいなと思いました。
| \end{align*} | ||
| $$ where $P_{j}:= \underset{0\le i < j } {\text{maximize}} \sum_{k=i}^{j-1}\text{diffs}[k]$. | ||
| - $P_{1} = \text{diffs}[0]$ かつ $j>1$に対して$$ | ||
| \begin{align*} |
There was a problem hiding this comment.
TeX表記がGitHubで正しく表示されていないみたいです。レビューするときも、ソースファイルの方を見るので、レビュワーにとっては好ましくなさそうです。
There was a problem hiding this comment.
ご指摘ありがとうございます.取り急ぎ,数式が正しく表示されるように修正しました.
また,今後数式を書くことがあれば,ソースファイルのほうで十分理解できる表記を用いようと思います.
| def maxProfit(self, prices: List[int]) -> int: | ||
| acc_mins = itertools.accumulate(prices, min) | ||
| max_profit = 0 | ||
| for buying, selling in zip(acc_mins, prices): |
There was a problem hiding this comment.
計算上問題は発生しないですが、当日までの最低価格ではなく、前日までの最低価格を使った方が直感的かと思いました。
There was a problem hiding this comment.
確かにそちらの方が直感的な説明と対応がとりやすいですね.
その方針でも改めて書いてみようと思います.
fix equations
| <summary> 分割統治による $O(N \log N)$ 時間アルゴリズム </summary> | ||
|
|
||
| - [アルゴリズムイントロダクション](https://www.amazon.co.jp/%E3%82%A2%E3%83%AB%E3%82%B4%E3%83%AA%E3%82%BA%E3%83%A0%E3%82%A4%E3%83%B3%E3%83%88%E3%83%AD%E3%83%80%E3%82%AF%E3%82%B7%E3%83%A7%E3%83%B3-%E7%AC%AC3%E7%89%88-%E7%B7%8F%E5%90%88%E7%89%88-%E4%B8%96%E7%95%8C%E6%A8%99%E6%BA%96MIT%E6%95%99%E7%A7%91%E6%9B%B8-%E3%82%B3%E3%83%AB%E3%83%A1%E3%83%B3/dp/476490408X), CLRS, の分割統治の章で見たことある. | ||
| - 階差を作って部分配列の和の最大化問題に言い換え→"真ん中"をまたぐ部分配列なら線形で最適解を計算出来て,またがない奴は部分問題の解になるから,(またぐやつ, またがない左右の部分問題) の3つに分割して統治する,という方針だったはず. |
There was a problem hiding this comment.
私も初めてみました。
この問題を見て最大部分配列問題として捉えるのは視点の幅がすごいなと思いました。
| if begin == end: | ||
| return 0 |
There was a problem hiding this comment.
begin == endのケースってあるんだっけ?と思ったら、株価データが一つしかないときのベースケースなんですね。
この下でend - begin == 1のベースケースがあるので、そこにたどり着くことなくbegin == endになるデータタセットがあるはずと考えるとすごく納得いきました。
| sell_today = diffs[0] | ||
| max_prof = sell_today |
There was a problem hiding this comment.
sell_todayが動詞っぽく感じられて、またprofと対応していない感じがしたので、対応関係があるとわかりやすいなと思いました。
local_max_prof, global_max_profなど
There was a problem hiding this comment.
仰る通りだと思います.動詞から始まると関数名のようにも受け取れる点からも,変数名に改善の余地がありましたね.
「profと対応していない」のようにほかの変数との兼ね合いという観点も大事だと思います.今後意識してみます.ありがとうございます.
|
|
||
| ``` | ||
|
|
||
| - `prices` が空配列のときと`len(prices) == 1`のときの扱いはステップ1のコードと同じように0を返す扱いをすることにした |
There was a problem hiding this comment.
空配列は問題上想定されていないですが、想定されていない入力値にはエラーで返したいという気持ちが個人的にはあります。
状況によってどちらかがいいかは変わると思います。
There was a problem hiding this comment.
仰る通り,どちらかに明確な(状況に左右されない)優劣が存在しないと思いますので,状況により適切な方を選べるようになっていることが大事なのかなと考えています.
| def maxProfit(self, prices: List[int]) -> int: | ||
| if len(prices) < 2: | ||
| return 0 | ||
| price_changes = [ |
There was a problem hiding this comment.
changesのほうが時系列のニュアンスがあるみたいですね。
私はdiffsしか思いつかなかったのでとても良い表現だと思いました。
| - `maxProfit` の `if begin == end` → `if begin >= end: ~` に変更し, (`maxProfit`側でチェック済みだけれども)残しておく | ||
| - カバレッジは減ってしまう(この点では`assert begin < end` のほうが良い) |
There was a problem hiding this comment.
こちらも個人的には入力として想定していないものはエラーにしたい気持ちがありassert推しです。
There was a problem hiding this comment.
__find_center_crossing_max_profit の方はfloat("-inf")を返しているので、privateメソッドではfloat("-inf")を返してpublicのmax_profitのところでどう返すか調整するでも良さそうと思いました。
There was a problem hiding this comment.
こちらも個人的には入力として想定していないものはエラーにしたい気持ちがありassert推しです。
__max_profit はprivateなメソッドなので,よりassertで済ましてしまいやすいかとは思います(制約を破るのはユーザではなくprivateなメソッドを呼び出す実装側の人=同僚や未来の自分なので).一方,仕様追加・修正の必要に迫られたときにこの__max_profit をより広いユースケースでもなるべく使えるように実装しておくことも,それはそれでありだとは思います.こちらについても,両方見えてくのがベターなのでしょうね.
__find_center_crossing_max_profit の方はfloat("-inf")を返しているので、privateメソッドではfloat("-inf")を返してpublicのmax_profitのところでどう返すか調整するでも良さそうと思いました。
こちらの方針とも迷ったのですが,「最大利益は0以上の値を返す」という知識が max_profitにも__max_profitにも埋め込まれてしまうのを嫌って,このように意思決定したと記憶しています.
| <details> | ||
| <summary> 名前がついてる線形時間アルゴリズム</summary> | ||
|
|
||
| - なんか線形で解くやつに名前が付いていて,最大化問題を適宜書き換えると帰着できたはず |
There was a problem hiding this comment.
これだけ丁寧に数式に落とされていてすごいと思いました。
私が数学苦手なのもあり、読み解くのに時間がかかりましたが大変勉強になりましたmm
| ] | ||
| return self.__max_profit(price_changes, 0, len(price_changes)) | ||
|
|
||
| def __max_profit(self, price_changes: List[int], begin: int, end: int) -> int: |
There was a problem hiding this comment.
同じmax_profitの名前でpublicなものをinterface, privateなものを実際の計算アルゴリズムとして分けた実装がとてもいいなと思いました。
pythonだとよくこういう書き方をするのでしょうね。普段使っていないので勉強になりました。
| def maxProfit(self, prices: List[int]) -> int: | ||
| if len(prices) < 2: | ||
| return 0 | ||
| today_max_profit = 0 |
There was a problem hiding this comment.
"その日"というニュアンスでtoday("今日")が使われているのが少し違和感持ちました。(non-nativeです;)
today→current yesterday→previousのほうが時間軸が固定されずに良い気がしました。
There was a problem hiding this comment.
たしかに,どちらかというとループ中における"This day" であってTodayではないですね.ちょっとLLMとも相談しながら英語のニュアンスとして適切なものを探して,ステップ4として後日作成しようと思います.ありがとうございます.
問題
https://leetcode.com/problems/best-time-to-buy-and-sell-stock/description/
成果物所在
121._Best_Time_to_Buy_and_Sell_Stock/memo.mdがステップ1~3までをまとめたmdファイルです.次に取り組む問題のリンク
https://leetcode.com/problems/permutations/description/