142. Linked List Cycle II - #2
Conversation
There was a problem hiding this comment.
よく書けていると思うんですが、全体的に空行が多いような気はします。
https://docs.google.com/document/d/11HV35ADPo9QxJOpJQ24FcZvtvioli770WWdZZDaLOfg/edit?tab=t.0#heading=h.59zydabj6a98
これは減らそうと思って減らすというよりは、自然と減る、という方が感覚として近いのですが。
There was a problem hiding this comment.
おっしゃる通り、他言語とは違ってネストである程度各節の分離を表現できるので、見返してみると冗長に感じる部分がありますね。
全体的に改行をなくしてみました。
https://github.com/kazizi55/coding-challenges/pull/2/files#diff-d7b00169799753b697b7185c622e016e009ca1ca042509631517f45196a88e95R1-R46
| finder = finder.next | ||
| catchup_node = catchup_node.next | ||
|
|
||
| return finder No newline at end of file |
There was a problem hiding this comment.
細かいですが、ファイルの最後には newline があったようがよいですかね (GitHub のプレビューでノイズになるとか、https://stackoverflow.com/questions/729692/why-should-text-files-end-with-a-newline とかの議論があると思います)。
There was a problem hiding this comment.
確かにそうですね。盲点でした、ありがとうございます。
全体的に new line を追加しました!
加えて、vscode で save 時に自動で new line が追加されるようにしてみました。
https://stackoverflow.com/questions/44704968/visual-studio-code-insert-newline-at-the-end-of-files
|
|
||
| return None | ||
|
|
||
| catchup_node = find_catchup_node(head) |
There was a problem hiding this comment.
個人的には catchup_node と finder という命名はわかりにくく感じました。catchup だと追いつく、という意味合いですが、実際は、catchup の方が先行する node ではありませんか。
その点では leading_node と following_node というほうがしっくりきます。
一例ですが、私なら関数名も変更してしまって
node_from_intersection = find_intersection(head)
node_from_head = headといった感じにするかなあ、と思いました。catchup という言葉と実際の処理の認識にずれがある気がします。
There was a problem hiding this comment.
おっしゃる通りですね。ありがとうございます。
fast & slow の概念もこのステップでは捨象されているので、追いつく・追いつかれるという表現自体が合わないなと改めて考えてみて思いました。なので lead & follow よりも intersection の変数名の方がしっくりきました。
intersection を使う形で全体的に書き換えました。
https://github.com/kazizi55/coding-challenges/pull/2/files#diff-d7b00169799753b697b7185c622e016e009ca1ca042509631517f45196a88e95R27-R46
|
|
||
| 関数化しない場合、2 つ目の while で fast, slow を使い回すのではなくて、origin という新しい変数に代入することもできる。が、origin というと個人的に何かの起源を思い浮かべるのと、origin なのは最初だけでどんどん探査していくことから、finder を使うのが適切だと判断した。 | ||
| https://github.com/olsen-blue/Arai60/pull/2/files#diff-2d9cbcab205d34e01955561fc8d084614d1d817a33b81d59bdbf2096fb2bd565R158-R163 | ||
|
|
There was a problem hiding this comment.
見てそうですがこのあたりにまとめてあるコードの整え方にざっと目を通しておくといいでしょう。
https://docs.google.com/document/d/11HV35ADPo9QxJOpJQ24FcZvtvioli770WWdZZDaLOfg/edit?tab=t.0#heading=h.9kpbwslvv3yv
There was a problem hiding this comment.
共有ありがとうございます。
https://discord.com/channels/1084280443945353267/1192728121644945439/1194296454181826590
いろいろ自明な書き換えってあるはずで、その中のどれを「技術的に選択」するかという問題です。
各条件や変数、処理の関係をいかにわかりやすく提示するかを技術的に選択するということですね。勉強になります。
https://discord.com/channels/1084280443945353267/1221030192609493053/1225674901445283860
X,Y,Zと処理があったときにそれぞれがどれくらい複雑か、関連しあっているかで1-4のパターンから技術的に選択できるかということですね。
|
|
||
| #### Hare and Tortoise | ||
|
|
||
| 平均 2 分で 3 回とも AC。 |
There was a problem hiding this comment.
はい、肝に銘じます。
加えて、Step3 に取り組んだ時点でACしていることは自明なので、次回以降は書かないようにします。
| @@ -0,0 +1,65 @@ | |||
| class SetSolutionWithRecursion: | |||
| def detectCycle(self, head: Optional[ListNode]) -> Optional[ListNode]: | |||
| visited = set() | |||
There was a problem hiding this comment.
Recursion、関数の返り値にset()を置くケースを見たことがあるんですが、やっぱり最初にvisited = set()を定義してくれる方が個人的に嬉しいですね。
There was a problem hiding this comment.
Recursion、関数の返り値にset()を置くケースを見たことがあるんですが
こんな感じで書けますね。盲点でした。ありがとうございます!
class SetSolutionWithArgument:
def detectCycle(self, head: Optional[ListNode]) -> Optional[ListNode]:
def find_node(node, visited):
if node is None:
return None
if node in visited:
return node
visited.add(node)
return find_node(node.next, visited)
return find_node(head, set())やっぱり最初にvisited = set()を定義してくれる方が個人的に嬉しいですね。
同感です。何に格納されるのかが最初に提示されていた方がわかりやすいし自然な感覚があります。
| while True という形で無限ループを使うことで None を早期 return できる。 | ||
| https://github.com/olsen-blue/Arai60/pull/2/files#diff-2d9cbcab205d34e01955561fc8d084614d1d817a33b81d59bdbf2096fb2bd565R149-R156 | ||
|
|
||
| 関数化しない場合、2 つ目の while で fast, slow を使い回すのではなくて、origin という新しい変数に代入することもできる。が、origin というと個人的に何かの起源を思い浮かべるのと、origin なのは最初だけでどんどん探査していくことから、finder を使うのが適切だと判断した。 |
|
Step2の解答比較の時点でかなりフィードバックがかかっているので、見習いたいなと思いました。書いてあるコードにほぼ文句がないです。かなり良いと思いました。 |
| finder = finder.next | ||
| catchup_node = catchup_node.next | ||
|
|
||
| return finder No newline at end of file |
There was a problem hiding this comment.
(重複があったので削除したら、2つとも消えたので再掲します)
3stepsが終わったらコンパイラーだけではなく、最後にフォーマッターが何を指摘してくるか予想できるようになると良いと思います。
条件分岐ごとの空行検出は定かではないですが、最後の改行などに関しては検出されるはずです(規約によるかもですが)。
mura0086/arai60#21 (comment)
https://discord.com/channels/1084280443945353267/1196498607977799853/1356188787377700905
There was a problem hiding this comment.
条件分岐ごとの空行検出は定かではないですが、最後の改行などに関しては検出されるはずです(規約によるかもですが)。
Black, Ruff, autopep8 をとりあえず試してみましたが、おっしゃる通り、最後の改行は検出されました。
条件分岐ごとの空行検出は少なくともデフォルトだと特に指摘されなかったですね。
doc を一通り読んでみましたが、条件分岐ごとの空行検出はなさそうでした。
https://black.readthedocs.io/en/stable/the_black_code_style/current_style.html
https://docs.astral.sh/ruff/rules/
https://pypi.org/project/autopep8/
pep8にもあるように、top-level function や class definition など以外は個人の裁量に委ねられているようですね。
Blank lines may be omitted between a bunch of related one-liners (e.g. a set of dummy implementations).
Use blank lines in functions, sparingly, to indicate logical sections.
|
|
||
| class HareAndTortoiseSolutionWithFunction: | ||
| def detectCycle(self, head: Optional[ListNode]) -> Optional[ListNode]: | ||
| def get_catchup_node(node): |
There was a problem hiding this comment.
get_catchup_node の意味するところは「fast がslow に catch up したnode を返す」だと思うんですが、初見でそのように読み取れませんでした。
それと、get で始まるメソッドはメンバ変数を返すだけの軽量なアクセサであるという印象を与えるため、重い計算をするメソッドには不適であるという意見もあります。
“多くのプログラマは、getで始まるメソッドはメンバの値を返すだけの「軽量アクセサ」であるという規約に慣れ親しんでいる。この規約を守らなければ、誤解を招く可能性がある。”
リーダブルコード 3章 誤解されない名前
There was a problem hiding this comment.
確かに関数名からは読み取りきれないですね。ありがとうございます。
get で始まるメソッドはメンバ変数を返すだけの軽量なアクセサであるという印象を与える
Java などだと getter を定義するのに get の prefix を使うので、確かにその印象は受けますね。
find_intersection という名前に変えてみました。
https://github.com/kazizi55/coding-challenges/pull/2/files#diff-d7b00169799753b697b7185c622e016e009ca1ca042509631517f45196a88e95R27-R28
| return node | ||
|
|
||
| visited.add(node) | ||
| return check_node(node.next) |
There was a problem hiding this comment.
python は末尾再帰最適化を実装していないようなので、データサイズによってはstack overflow になってしまうかもしれません。
https://stackoverflow.com/questions/13591970/does-python-optimize-tail-recursion
Does Python optimize tail recursion?
No, and it never will since Guido van Rossum prefers to be able to have proper tracebacks
There was a problem hiding this comment.
python は末尾再帰最適化を実装していないようなので
知らなかったです。ありがとうございます!!
実際に試してみましたが、今回の例だと1497個目くらいから実際に maximum recursion depth exceededのエラーが出ました。とするとやはり中〜大規模な処理をさせるのに少なくともPythonにおいては末尾再帰を使った手法は使わない方が良さそうですね。
Testing Set with Recursion...
Error in test case 1497: maximum recursion depth exceeded
Description: Large list, cycle at beginning
Error in test case 1498: maximum recursion depth exceeded
Description: Large list, cycle at end
Error in test case 1499: maximum recursion depth exceeded
Description: Large list, cycle in middle
Execution time: 0.4523 seconds
No, and it never will since Guido van Rossum prefers to be able to have proper tracebacks:
あとこれも勉強になりました。
デバッグの際にスタックトレースが分かりにくくなることを避けたかったのですね。
https://leetcode.com/problems/linked-list-cycle-ii/
Next: https://leetcode.com/problems/remove-duplicates-from-sorted-list/