FP:リストの純粋処理で、過去と未来の共存をめざす
このワークシートはMath by Codeの一部です。
データ構造を学ぶと、スタックが便利だと気づく。
pythonではリストXlstに対して、
Xlst.append(x)で、Xlst.push(x)関数が実現できる。
Xlst.pop()はもともとある。
なんて便利なんだろう
と思ったことはありませんか。
Pythonではスタック処理がカンタンにかける。すばらしい!
それだけではない。append,popの仲間がいる。
Xlist.extend(Ylist)で、リストの合併もできるし、
Xlist.insrt(idx, x)でidxにxを追加でき、
Xlist.remove(x)で要素xが削除でき、Xlist.pop(idx)でidx番の要素が削除できて、
Xlist.sort()で並べ替えできて、Xlist.reverse()で逆順にできて、。。。。
やりたい放題です。
初めのうちはこれは便利と思うかもしれません。
リスト自体が可変なオブジェクトだから変えて何が悪いのと思うかもしれません。
でも、デバックを考えると危険なワナです。
変えてしまったものは2度とあとからたどれないのです。
返るたびにコンソールにログを出したり、print文を差し込んだりもできるので、
たしかにそうです。
しかし、どこにエラーがあるかわからないとprint文だらけになったりしますね。
事前にエラーをさけるために、トライキャッチ構文で包んだり
いろいろな手法はありません。
何を使うにしても、過去の変数の中身が破壊されずに残っていてそれをたどることができないと
大変なのです。
ということで、今回はリスト処理を純粋にして、メモリ不変を目指そう。
1.メモリ書き換えの所業をあばく
不変性のないメモリ改造の実態をテレビ番組風にすると
こうなります。
#昔住んでいた3階建ての住居
Xlst = [3, 1, 2]
print(f"ビフォー (ID: {id(Xlst)}): {Xlst}")
# 1. 屋上を追加。
Xlst.append(4)
# 2. さらに2階分ふやす。
Xlst.extend([5, 6])
# 3. 一度解体して、どこにでもある単純な構造に改造される。
Xlst.sort()
print(f"アフター (ID: {id(Xlst)}): {Xlst}")
#なんということでしょう。同じ住所に6階建ての見る影もないマンションがたっていた。
[OUT]
ビフォー (ID: 2502425525632): [3, 1, 2]
アフター (ID: 2502425525632): [1, 2, 3, 4, 5, 6]
劇的ビフォーアフターの大改造、
ではなく、大破壊ですね。
喜んでくれるでしょうか。
直してくれとは言ったけど、見る影もなく破壊してくれとはいってないはずですね。
もう、[3, 1, 2]というつながりも風景も消えてしまいました。
まだ、1,2,3という中身が残っているのが救いでしょうか。
2.処理をクリーンに改造すれば、過去も現在も両立できる
< L.append(x) ➔ L+ [x]>
#リスト+1要素xは、リスト+xのリストで、新築します。
def pure_push(original_list, value):
return original_list + [value]
Xlst_v0 = [3, 1, 2]
Xlst_v1 = pure_push(Xlst_v0, 4)
print(f"Xlst_v0 (過去):(ID: {id(Xlst_v0)}) {Xlst_v0}")
print(f"Xlst_v1 (現在):(ID: {id(Xlst_v1)}) {Xlst_v1}")
[OUT]
Xlst_v0 (過去):(ID: 2502425566720) [3, 1, 2]
Xlst_v1 (現在):(ID: 2502425560000) [3, 1, 2, 4]
3階建ての家に屋上をつけたと思ったら、過去の3階建ては残っていて、となりに屋上つきの3階建てができました。
<L.extend(Y) ➔ L+Y>
#リスト合併も全く同じです。`+` 演算子は、両者の歴史を守ったまま、新築します。
#<.extend()` ➔ `+`>
#リスト合併も全く同じです。`+` 演算子は、両者の歴史を守ったまま、新築します。
Xlst = [3, 1, 2, 4]
Ylst = [5, 6]
Zlst = Xlst + Ylst
print(f"Xlst (過去):(ID: {id(Xlst)}) {Xlst}")
print(f"Ylst (過去):(ID: {id(Ylst)}) {Ylst}")
print(f"Zlst (現在):(ID: {id(Zlst)}) {Zlst}")
[OUT]
Xlst (過去):(ID: 2502425566656) [3, 1, 2, 4]
Ylst (過去):(ID: 2502425559552) [5, 6]
Zlst (現在):(ID: 2502425526080) [3, 1, 2, 4, 5, 6]
<L.sort() ➔ sorted(L)>
#.sortは改造、sorted()は新品を返します。
lovely = [3, 1, 2, 4, 5, 6]
frozen = sorted(lovely)
print(f"lovely (過去):(ID: {id(lovely)}) {lovely}")
print(f"frozen (現在):(ID: {id(frozen)}) {frozen}")
[OUT]
lovely (過去):(ID: 2502400105024) [3, 1, 2, 4, 5, 6]
frozen (現在):(ID: 2502425559808) [1, 2, 3, 4, 5, 6]
< .remove(),.pop() ➔ Comprehension(リスト内包表記)/Slice(スライス)>
# 要素2を削除したいremove(2)の代わり
# x=2以外の要素でリストを作る
Xlst_v0 = [1, 2, 3, 2, 4]
Xlst_v1 = [x for x in Xlst_v0 if x != 2] # x!=2のリスト内包表記(Comprehension)
# pop(2)で popAt=2の要素を削除する代わり、
# popAtの手前スライス(Slice)とpopAtの後ろスライス(Slice)を合併する
popAt = 2
Xlst_v2 = Xlst_v0[:popAt] + Xlst_v0[popAt + 1:]
print(f"Xlst_v0 (過去):(ID: {id(Xlst_v0)}) {Xlst_v0}")
print(f"Xlst_v1 (現在):(ID: {id(Xlst_v1)}) {Xlst_v1}")
print(f"Xlst_v2 (現在):(ID: {id(Xlst_v2)}) {Xlst_v2}")
[OUT]
Xlst_v0 (過去):(ID: 2502425030848) [1, 2, 3, 2, 4]
Xlst_v1 (現在):(ID: 2502425559232) [1, 3, 4]
Xlst_v2 (現在):(ID: 2502425560000) [1, 2, 2, 4]
さて、メモリ新築によって、過去も改造した現在も両方残るというロジックと
メモリの変化の事実が納得できましたか。
これが、予見可能性による、デバッグしやすさの実態です。
3.ビフォーアフターのビフォーアフター
<ビフォーアフターのビフォーアフター>
1でビフォーアフターとして、
同じ住所に別物が立って、過去は記憶だけの世界になってしまいました。
これがビフォーだとしたら、
同じ処理群をリストにおこなっても過去も現在も両立できる
これがアフターです。
ビフォーアフターのアフターを検証してみよう。
# FP版:思い出の住まいと改築の歴史がアルバムのように並列するクリーンなメモリ世界】
# 1. 昔住んでいた3階建ての住居(v0)
X_v0 = [3, 1, 2]
print(f"v0の住所 (ID: {id(X_v0)}): {X_v0}")
# 2. 屋上を追加した「新しい家」を、別の住所に新築(v1)
X_v1 = X_v0 + [4]
# 3. さらに2階分増やした「新しい家」を、また別の住所に新築(v2)
X_v2 = X_v1 + [5, 6]
# 4. 単純な構造に改造された「最新のマンション」を、これまた別の住所に新築(v3)
X_v3 = sorted(X_v2)
print("\n--- なんということでしょう ---")
print(f"現在の最新マンション (ID: {id(X_v3)}): {X_v3}")
print(f"あの頃の3階建ての住居 (ID: {id(X_v0)}): {X_v0}") # 記憶ではなく、メモリ空間に無傷で存在している過去の住まい。
[OUT]
v0の住所 (ID: 2502425566976): [3, 1, 2]
--- なんということでしょう ---
現在の最新マンション (ID: 2502425554304): [1, 2, 3, 4, 5, 6]
あの頃の3階建ての住居 (ID: 2502425566976): [3, 1, 2]
<おまけ>
なお、おまけですが、初心者ならどんどん使いそうな指定番地の書きかえ。L[x]=y
この改造はもうお分かりですね。
PopAt=xと考えれば、Popの代わりの書き方でPopAt前+[y]+PopAt前と
スライスサンドにすればよいですね。
課題:Geogebraでリストのメモリの新築、過去の改造歴史が残ることを実感できるものをつくろう。
タイトルを「geogebraはリストメモリを新築する。」として、
f={3,1,2}
g=join(h,{4})
h=join(g,{5,6})
i=sort(h)
これらの数式が別名でリレーされ、数式ビューに1行ずつ残りますね。
Pythonはメモリありきで、名前は後付けですが、
geogebaは名前ありきで、メモリを取ります。
だから、名前が別なら新築メモリということになるはずですね。
text1="f="f+",\\g="+ g+", \\h="+ h+", \\i="+i
text1を表示ONにして、クリックし、LaTex数式をOnにしてみましょう。
数式がfからiまで綺麗に並列してますね。