Conversation
| ```python | ||
| class Solution: | ||
| def subsets(self, nums: List[int]) -> List[List[int]]: | ||
| subsets = [] |
There was a problem hiding this comment.
変数名が関数名と被っている点に違和感を感じました。 all_subsets としてはいかがでしょうか?
| subsets = [] | ||
| subset = [] | ||
|
|
||
| def backtrack(index): |
There was a problem hiding this comment.
backtrack は、再帰の戻り掛けに何らかの処理を行うことを表す単語です。今回は戻り掛けに処理を行っていないため、 backtrack という単語は不適切だと思います。 traverse あたりはいかがでしょうか?
There was a problem hiding this comment.
コメントありがとうございます。backtrack関数が呼ばれる前にsubsetに要素を追加し、抜けた後に要素をpopして元に戻している(backtrack)のでbacktrackかなと思います。DFSの1種でもあるのでtraverseでも違和感はないと思います。
|
よいと思います。 |
|
|
||
| make_subsets(0) | ||
| return all_subsets | ||
| ``` |
There was a problem hiding this comment.
これなんですけど、自分は上司部下のたとえで再帰をとらえているので、
部下に「インデックスi+1以降でできたsubsetの各リストに、自分が渡すpartial_subsetリストの要素が入ったものを、別途コピーしてall_subsetsに加えて、それを返して」と頼む。さらに「自分に返す時には、君が入れたpartial_subsetの値はもとに戻してね」とも頼まなければならない。
という感じで個人的にはややこしくコードを追うのに苦労してしまいました。
自分の経験が余り足りてないのもあるかもですが、こういう処理の仕方はよくあるものなのでしょうか。それとも違う感じでコードを捉えていますか。昔のにコメントしてすみません。
There was a problem hiding this comment.
うーん、そもそも自然言語での記述がなんか正しくコードを追えていないような気がします。
こういう処理の仕方はよくあるものなのでしょう
質問に答えると、あると思います。選択肢としては、partial_subsetsを引数で取り回すかグローバル変数としてヘルパー関数の外で定義するかくらいでしょうか。
There was a problem hiding this comment.
@nittoco さんが躓いているポイントかは分かりませんが、partial_subsets(複数形)より、partial_subset(単数形)の方が正しい名前だと思います。
There was a problem hiding this comment.
@nittoco さんと同じく処理を追うのに苦労したので、自分なりに原因を言語化してみました。今後、このPRを閲覧する学習者の参考になればと思いコメントします。
部下に「インデックスi+1以降でできたsubsetの各リストに、自分が渡すpartial_subsetリストの要素が入ったものを、別途コピーしてall_subsetsに加えて、それを返して」と頼む。さらに「自分に返す時には、君が入れたpartial_subsetの値はもとに戻してね」とも頼まなければならない。
これは、ホワイトボード (partial_subsets) を部下と共有していて、「担当インデックスの数字をホワイトボードに書き足して、さらに部下に同じことを依頼して。で、部下から完了報告を受けたら、君が入れた数字を消して元に戻して、もう一度部下に依頼して」が近いように感じました。(「最後なら、ホワイトボードの写真を撮ってバインダーにファイルしておいてね」もありますね)
ただし正確には、もとのコードでは、担当の数字を書き足す前に部下へ回してしまっており、自然言語での説明が難しくなっています。以下の順序だと上記の説明どおりになっており、比較的理解しやすいのではないでしょうか。
partial_subsets.append(nums[index])
make_subsets(index + 1)
partial_subsets.pop()
make_subsets(index + 1)で、何が理解を難しくしているのかというと、
- 部下とのやり取りを引数・戻り値ではなく、外側の変数を通して行っている(かつ、引数も利用している)
- 部下への依頼が非同期的である:これまでは部下に命令を与えると、仕事の結果をすぐに受け取ることができた。が、ここでは、さらに次の部下へ依頼させる、その結果を受け取った後でもう一度部下に依頼させる
あたりの要素があり、結果として partial_subsets の中身とコールスタック(未処理の再帰呼び出しとその引数)を頭の中でシミュレートすることが、Arai60の他の問題では無かったパターンのように感じました。
There was a problem hiding this comment.
自分はそれほど読みにくいとは感じませんでした。ただ、自分なら処理の区切りに適宜空行を入れると思いました。
また、関数の外側にある all_subsets、 partial_subsets を通してやり取りを行っているという点については、分かりにくいと感じる感覚は理解できました。
以下だと少しわかりやすくなるでしょうか。
class Solution:
def subsets(self, nums: List[int]) -> List[List[int]]:
def make_subsets(index, partial_subsets, all_subsets):
if index == len(nums):
all_subsets.append(partial_subsets.copy())
return
# nums[index]をpartial_subsetsに追加しない場合。
make_subsets(index + 1, partial_subsets, all_subsets)
# nums[index]をpartial_subsetsに追加する場合。
partial_subsets.append(nums[index])
make_subsets(index + 1, partial_subsets, all_subsets)
partial_subsets.pop()
partial_subsets = []
all_subsets = []
make_subsets(0, partial_subsets, all_subsets)
return all_subsetsThere was a problem hiding this comment.
まあ、言いたいことは分かります。
暗黙のうちに「make_subsets は partial_subsets の状態を元に戻さなくてはいけない」ということを部下全員が守っているので帰納的に守られているのですが、これは読み取らないといけません。
https://leetcode.com/problems/subsets/