Create Zigzag Conversion.md#6
Conversation
| def zigzag_solution(self, s: str, numRows: int) -> str: | ||
| reordered_chars_list = [""] * numRows | ||
| height = 0 | ||
| isForward = true |
|
|
||
| #### step1 | ||
| 文字列とNが渡されて順番を規則的に動かすもの | ||
| 方針1:個数NのVector(作成時点では配列長不明な配列)を作り、それぞれにPushしていく方式。Python3のstrの配列はこの条件を満たす。Stringのほうが簡便か。 |
There was a problem hiding this comment.
str の += は毎回新しいインスタンスが作られるため、 list() に追加していく場合に比べて時間計算量が上がる点が気になりました。
https://github.com/python/cpython/blob/main/Objects/unicodeobject.c#L11603
There was a problem hiding this comment.
"str の += は毎回新しいインスタンスが作られる"ことを知らずに書いていました。手元で実験してみても数百倍の差があるので、ご指摘の通り、list()に追加する形のほうが良いですね。
import timeit
# 文字列に += で追加
code_str = "s = ''; [s := s + 'a' for _ in range(100000)]"
# リストに append
code_list = "ary = []; [ary.append('a') for _ in range(100000)]"
# 時間計測
number = 1
time_str = timeit.timeit(code_str, globals=globals(), number=number)
time_list = timeit.timeit(code_list, globals=globals(), number=number)
print(f"str += 'a': {time_str:.6f} 秒")
print(f"list.append('a'): {time_list:.6f} 秒")~/Projects/sandbox $ python3 str_time.py
str += 'a': 0.976869 秒
list.append('a'): 0.002893 秒There was a problem hiding this comment.
計測方法に違和感を感じました。
+=演算子と:=は別のものです。今回は+=について論じているため、+=演算子を使うべきです。- 今回の問題設定に合わせるのであれば、 ary に 'a' を append() したあと、 join() して一つの文字列にしなければならないと思います。
手元で計測しなおしたところ、異なる結果が出ました。
import timeit
def do_str():
s = ""
for _ in range(100000):
s += "a"
def do_list():
ary = []
for _ in range(100000):
ary.append("a")
pass
def main():
time_str = timeit.timeit(do_str, number=1000)
print(f"str += \"a\": {time_str:.6f} 秒")
time_list = timeit.timeit(do_list, number=1000)
print(f"list.append(\"a\"): {time_list:.6f} 秒")
if __name__ == "__main__":
main()str += "a": 7.105431 秒
list.append("a"): 3.611834 秒
str += "a" のほう、 2 乗オーダーになると思っていたのですが、なっていないですね…。
調べ直したところ、 += 演算子は PyUnicode_Append() で処理されるようです。
https://github.com/python/cpython/blob/main/Objects/unicodeobject.c#L11650
また、 CPython では左辺値の参照カウントが 1 の場合、 in-place で追加されるため、高速になるようです。
ただ、この高速化手法はソフトウェアエンジニアの常識には含まれていない気がします。そのため、他のソフトウェアエンジニアが += のコードを読んだ時、 2 乗オーダーになるため遅くなるだろうと勘違いする可能性があります。ソースコードにコメントとして注意書きを書いておけば問題ないかもしれませんが、微妙なところです。
append() してから join() するほうは、平均的なソフトウェアエンジニアは線形だと分かると思いますし、ベンチマークの結果も速いため、こちらを使ったほうが良いと思います。 append() は動的配列で実装されており、 capacity が無くなるとメモリを再確保するものの、 1 回あたりの呼び出しの時間計算量は、償却時間 (ならし) で O(1) になる点に注意が必要です。 append() の挙動は常識に含まれると思いますので、一度調べておくとよいと思います。
| for char in s: | ||
| reordered_chars_list[height] += char | ||
| if height + direction < 0 or height + direction >= numRows: | ||
| direction *= -1 |
| ```python3 | ||
| class Solution: | ||
| def convert(self, s: str, numRows: int) -> str: | ||
| if numRows == 0: |
There was a problem hiding this comment.
ここは numRows == 1 の間違いなのか、意図して0にしているのかどちらですか?
後者であるとすると、0の場合に元の文字列を返していることに違和感があります。また、height + direction が0と-1を行き来することになり、長さ1の reordered_chars_list に対してアクセスする要素が常に同じになるので問題なく動くのですが、読み解くのに時間がかかりました。
他のコードでは1になっていますね。
There was a problem hiding this comment.
すいません、前者です。
Pythonだとindexが-1でout of rangeにならず、動いてしまっているんですね
This problem:
https://leetcode.com/problems/zigzag-conversion/description/
Next problem:
https://leetcode.com/problems/is-subsequence