Skip to content

Create Pow.md #9

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
122 changes: 122 additions & 0 deletions Pow.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
# 50. Pow(x, n)
ブランクが空いたので前回指定した「次に解く問題」ではなく、別の問題の別解として馴染みのある再帰の問題を選びました。

## 参考にした方々(Pythonで書かれた直近5名)
- https://github.com/nittoco/leetcode/pull/17/files
- https://github.com/Mike0121/LeetCode/pull/17/files
- https://github.com/Ryotaro25/leetcode_first60/pull/48/file
- https://github.com/Yoshiki-Iwasa/Arai60/pull/38/files

## Step 1
### 考えたこと
- まずは再帰で書いてみる。
- n = 0の場合をベースケースとして、+-で掛け算と割り算を繰り返せば良いと思った。他の方のコードで呼び出し上限について言及してるの見たことあるな、こういう風に困るのか…。
- が、呼び出し回数に引っかかって通らない。ここでギブアップ。

下記、通りません:
```Python
class Solution:
def myPow(self, x: float, n: int) -> float:
if n == 0:
return 1
elif n <= -1:
Copy link

Choose a reason for hiding this comment

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

私は上の条件が return ならば、elif よりも if を好みます。

Copy link
Owner Author

Choose a reason for hiding this comment

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

elif以降を気にしながらifに続く処理を読む分の負荷が不要になるためですかね。であれば納得感があるので、自分もifを採用してみようと思います(違ったら教えてください)。

return self.myPow(x, n + 1) / x
else:
return x * self.myPow(x, n - 1)
```

- ここで答えを見ると、x^n = (x^(n/2))^2の操作を繰り返すと計算回数を減らせると知ったので書き直してみる。
- 愚直にPowを2回呼び出すと結局再帰呼び出し回数に引っかかってしまうので、temp的な変数(half…)に入れ直す
- nが小数の時とかどうしてるんだろう→先人のgitにPowのプログラムへのリンクがあり、生まれて初めてCPythonのソースコードを読んでみたい気になる。が、手も脚も出ない:https://github.com/python/cpython/blob/d5ba4fc9bc9b2d9eff2a90893e8d500e0c367237/Objects/longobject.c#L4849

```Python
class Solution:
def myPow(self, x: float, n: int) -> float:
if n == 0:
return 1
elif n < 0:
n = -n
x = 1 / x

if n % 2 == 0:
half_even = self.myPow(x, n / 2)
Copy link

Choose a reason for hiding this comment

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

n の型が int のため、割り算の結果の型が整数になるよう、 // 演算子を使ったほうが良いと思います。

return half_even * half_even
if n % 2 == 1:
Copy link

Choose a reason for hiding this comment

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

elif: のほうがシンプルだと思います。

Copy link
Owner Author

@SanakoMeine SanakoMeine Jan 21, 2025

Choose a reason for hiding this comment

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

ありがとうございます(むしろなぜifにしてたのか思い出せないので、整理途中のコードがそのまま残っていたものと思われます...)

Copy link

Choose a reason for hiding this comment

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

else にするか全体を下げるかでしょうかね。
偶数奇数での差は x をかけるかだけなので、もう少しまとめることもできます。

Step 3 あたりでなされているので、選択肢が見えているならば、あとは趣味の範囲かと思います。

Copy link
Owner Author

Choose a reason for hiding this comment

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

ありがとうございます。不慣れなだけかもしれませんが、全体を下げるのはifと対称でない様に感じられるのでelseにしそうです。

偶数奇数での差は x をかけるかだけなので、もう少しまとめることもできます

ありがとうございます、if文に入る前に計算してhalfに入れるなどしようと思います。

Copy link
Owner Author

Choose a reason for hiding this comment

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

half_odd = self.myPow(x, (n - 1) / 2)
return x * half_odd * half_odd
```


## Step 2
### 学んだこと
- モジュラ逆数?(https://github.com/nittoco/leetcode/pull/17/files) 公式へのリンクが貼ってあって、合同式の中で逆数をどう定義するかの話らしい ¶。例がピンとこないのでネットに落ちてた記事に頼る https://qiita.com/Cper0/items/1dc7e7db1eddcbfd259f。
- みんな浮動小数点の話をしてる。 → 2^(-n)の扱いや型を変換した際に丸め誤差などが乗ることを恐れているっぽい。確かにうっすら怖かったが、自分の場合それに対処できるほど意識を回せていない。とりあえずこれに目を通してみたところ、精度を改善するための方法がいろいろあるらしい:https://docs.python.org/ja/3.5/tutorial/floatingpoint.html
- 末尾再帰とその最適化という言葉を初めて知った。その関数自身のみを呼び出す形にすることで計算回数を減らす考え方らしい。:https://zenn.dev/kj455/articles/dfa23c8357b274
- 「再帰は若干認知負荷が高くて読みにくいよね」みたいな話が以前にもあったのでwhile文で書いてみる
- accumulated_prodはわかりやすくて良い。Cpythonは読めなかったけど、accumみたいな名前の付け方をしていた。
- 時間の都合でギブしたが後でもう一度この方のPRを読む:https://github.com/fhiyo/leetcode/pull/46/files。bit演算するアプローチも追えてないが、計算誤差避けの手段っぽい。

```Python
class Solution:
def myPow(self, x: float, n: int) -> float:
if n < 0:
n = -n
x = 1 / x
elif n == 0:
return 1
Comment on lines +65 to +66
Copy link

Choose a reason for hiding this comment

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

個人的には n == 0 の条件を先頭に回したほうが単純に見える気がしますね。(下ではそうしていますね。)

Copy link
Owner Author

Choose a reason for hiding this comment

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

僕もそちらの方が自然に感じます。繰り返し打っているステップ3の方が、なんとなく色んな負荷が軽減されたコードが出力される感じがある(「しっくりくる」「書きやすい」)ので、ご指摘いただいたやり方を採用します。


accum_prod = 1
Copy link

Choose a reason for hiding this comment

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

読むにあたり認知負荷がかかるため、単語から文字を削って略称にするのは避けることをお勧めいたします。

https://google.github.io/styleguide/pyguide.html#316-naming

avoid abbreviation. In particular, do not use abbreviations that are ambiguous or unfamiliar to readers outside your project, and do not abbreviate by deleting letters within a word.

Copy link
Owner Author

Choose a reason for hiding this comment

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

ありがとうございます、accumulated_productなどに変更します


while n > 0:
if n % 2 == 1:
accum_prod = accum_prod * x
x = x * x
n = n // 2
print(f"{accum_prod}")
Copy link

Choose a reason for hiding this comment

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

消し忘れですかね。
ちなみにご存知だったらすみませんが、Python3.8以降では、print(f"{accum_prod=}")ように{}内に=をつけると、accum_prod=10 みたいに変数名を合わせて表示されるので、複数の変数のデバッグに便利です。
https://docs.python.org/3/whatsnew/3.8.html#f-strings-support-for-self-documenting-expressions-and-debugging


return accum_prod
```

## Step 3

### コメント
- 先人のPRを見ているとwhileの方が可読性が高いのだろうと思いつつ、苦手だった再帰を書ける様にしておこう&めずらしく再帰の方がしっくり来たので、まずは再帰で3回通してみる。エンジニアリングの趣旨から逸脱するのが怖いので、whileでもその後練習してみる。
- 1回目:5:36, 2回目:3:42 3回目:2:15

### 再帰で書いた方
```Python
if n == 0:
return 1.0

if n < 0:
x = 1 / x
n = -n

if n % 2 == 0:
half_pow = self.myPow(x, n / 2)
Copy link

Choose a reason for hiding this comment

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

上のコメントと重なりますが、n // 2とした方がint型のままだな、というのが読んでいてはっきりわかるので不安がなさそうです。(この場合、分母が偶数だから、、、という推理が入る)
また、そのようにしたらhalf_powまでの処理が共通化できますね。

Copy link
Owner Author

@SanakoMeine SanakoMeine May 4, 2025

Choose a reason for hiding this comment

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

こうですね,ありがとうございます。

class Solution(object):
    def myPow(self, x, n):
        if n == 0:
            return 1.0
        if n < 0:
            n = -n
            x = 1/x

        half_pow = self.myPow(x, n//2)

        if n % 2 == 0: 
            return half_pow*half_pow
        else:
            return x*half_pow*half_pow

return half_pow * half_pow
else:
half_pow = self.myPow(x, (n - 1) / 2)
return x * half_pow * half_pow
```

### whileで書いた方
```Python
class Solution:
def myPow(self, x: float, n: int) -> float:
if n == 0:
return 1
if n < 0:
n = -n
x = 1 / x

accum_prod = 1

while n > 0:
if n % 2 == 1:
accum_prod *= x
x *= x
n //= 2

return accum_prod
```