Skip to content

【Arai60】50問目 46. Permutations#50

Merged
shining-ai merged 2 commits intomainfrom
review50
Jun 29, 2024
Merged

【Arai60】50問目 46. Permutations#50
shining-ai merged 2 commits intomainfrom
review50

Conversation

@shining-ai
Copy link
Copy Markdown
Owner

Copy link
Copy Markdown

@nodchip nodchip left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

よいと思います。

Copy link
Copy Markdown

@hayashi-ay hayashi-ay left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

良いのではないでしょうか。

# モジュールを使用
class Solution:
def permute(self, nums: List[int]) -> List[List[int]]:
return list(itertools.permutations(nums, len(nums)))
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

itertoolsにいるの知らなかったです。

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

https://github.com/python/cpython/blob/main/Modules/itertoolsmodule.c
そろそろ読みます? hayashi さん手伝ってあげてください。

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

pythonで書かれたコメントが何やっているかすら理解できませんでした...
cyclesの意図が汲み取れないです。

def permutations(iterable, r=None):
    # permutations('ABCD', 2) --> AB AC AD BA BC BD CA CB CD DA DB DC
    # permutations(range(3)) --> 012 021 102 120 201 210
    pool = tuple(iterable)
    n = len(pool)
    r = n if r is None else r
    if r > n:
        return
    indices = list(range(n))
    cycles = list(range(n, n-r, -1))
    yield tuple(pool[i] for i in indices[:r])
    while n:
        for i in reversed(range(r)):
            cycles[i] -= 1
            if cycles[i] == 0:
                indices[i:] = indices[i+1:] + indices[i:i+1]
                cycles[i] = n - i
            else:
                j = cycles[i]
                indices[i], indices[-j] = indices[-j], indices[i]
                yield tuple(pool[i] for i in indices[:r])
                break
        else:
            return

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

自分も見てみたんですけど、難しいですね。1時間ほど手元で動かしたりしたのですが、あまり分からなかったです。
C++のnext_permutationsとも違った動きをしてそうですね。

一応cyclesについては操作に必要な回数を管理していそうです。
末尾からみてx個についての順列を作成したらx+1個目に移るみたなのを管理しています。

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

わざわざ見ていただきありがとうございます。
一旦寝かせて、他の問題を解いてからまた戻ってきたいと思います。

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

とりあえず、上のコードについて。

その前に、yield はご存知ですかを一応確認。

indices は、オブジェクトを直接動かす代わりに、その位置を示すインデックスを示していて、これを入れ替えています。
cycles は、繰り上がりの周期を示しています。
たとえば、n=6, r=3 だと、[6,5,4] になるんですが、
"012345"から3つを取ると"012", "013",...となり、最後の桁は4周期、次の桁は(繰り上がりを1回と数えて)5周期、一番上の桁は6周期です。

for i in reversed(range(r)):
これは、後ろから数を増やす作業をします、ということです。
そして、else を見ると yield したあとに break しているので、出力したらループが終わることが分かります。

では、
for i in reversed(range(r)):
の内容を精査していきましょう。

cycles には、0桁目から r - 1 桁目の周期が並んでいますね。
今回、cycles[i+1] == 0 になったときを考えましょう。
i + 1 の桁まではちょうど一周したはずです。
だから、indices[i+1:] はソート済みです。
indices[i:] = indices[i+1:] + indices[i:i+1]
とすると、indices[i] の順番が入れ替わることになります。

こういうときには、printf debug が一番です。

def permutations(iterable, r=None):
    # permutations('ABCD', 2) --> AB AC AD BA BC BD CA CB CD DA DB DC
    # permutations(range(3)) --> 012 021 102 120 201 210
    pool = tuple(iterable)
    n = len(pool)
    r = n if r is None else r
    if r > n:
        return
    indices = list(range(n))
    cycles = list(range(n, n-r, -1))
    yield tuple(pool[i] for i in indices[:r])
    while n:
        for i in reversed(range(r)):
            cycles[i] -= 1
            if cycles[i] == 0:
                print("before", cycles, indices)
                indices[i:] = indices[i+1:] + indices[i:i+1]
                cycles[i] = n - i
                print("after", cycles, indices)
            else:
                j = cycles[i]
                indices[i], indices[-j] = indices[-j], indices[i]
                print("out", cycles, indices)
                yield tuple(pool[i] for i in indices[:r])
                break
        else:
            return

として、実行すると、次のような出力が得られました。
out [5, 1, 1, 1, 1] [0, 4, 3, 2, 1]
before [5, 1, 1, 1, 0] [0, 4, 3, 2, 1]
after [5, 1, 1, 1, 1] [0, 4, 3, 2, 1]
before [5, 1, 1, 0, 1] [0, 4, 3, 2, 1]
after [5, 1, 1, 2, 1] [0, 4, 3, 1, 2]
before [5, 1, 0, 2, 1] [0, 4, 3, 1, 2]
after [5, 1, 3, 2, 1] [0, 4, 1, 2, 3]
before [5, 0, 3, 2, 1] [0, 4, 1, 2, 3]
after [5, 4, 3, 2, 1] [0, 1, 2, 3, 4]
out [4, 4, 3, 2, 1] [1, 0, 2, 3, 4]
これでこれは読めるかしら。

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

その前に、yield はご存知ですかを一応確認。

関数の処理を中断して戻り値を返すものという理解で、仕組みは理解してないです。
大きなファイルの読み出しの時に使うくらいの認識です。

"012345"から3つを取ると"012", "013",...となり、最後の桁は4周期、次の桁は(繰り上がりを1回と数えて)5周期、一番上の桁は6周期です。

cyclesの意味をこちらのコメントで理解できて、読むことができました。

あとは、桁の移動の部分の判定も読み取れてなかったのですが、debugの結果を見て分かりました。

  • if cycles[i] == 0:の条件下ではbreakがないので次の桁に進める
  • elseの条件下ではbreakで最初の桁(r桁目)に戻る

breakでiの値がリセットされることを見落としていて苦戦しました...

def permute(self, nums: List[int]) -> List[List[int]]:

def make_next_permutation(nums):
for left in range(len(nums) - 2, -1, -1):
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pythonの言語仕様的には問題ないですけど、for文のiteratorで使用しているleftを後続の処理でも使っているのが少し分かりにくいかなと思いました。C言語とかだとfor文の中でしかleftを使うことができないので。

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

while でleftの位置を動かすような感覚で使っていましたが、確かにfor文で定義した変数をそのまま使うのは読みづらくなりそうですね。

min_right_num = nums[i]
right = i
nums[left], nums[right] = nums[right], nums[left]
nums[left + 1 :] = sorted(nums[left + 1 :])
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

このタイミングでleftの右側は降順になっていると思うので、reverseで良いです。

nums[left + 1 :] = sorted(nums[left + 1 :])
return True

nums_copy = nums[:]
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nums_copyよりいい命名があると思います。current_pamutationとか?もっと良いのがあるかも。


nums_copy = nums[:]
nums_copy.sort()
permutations = [nums_copy[:]]
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

自分ならこれもwhileの中でやっちゃいます。終了条件とかも合わせて変えないとですが。

@shining-ai shining-ai merged commit d01a108 into main Jun 29, 2024
@shining-ai shining-ai deleted the review50 branch June 29, 2024 14:09
@rihib rihib mentioned this pull request Aug 13, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants