FP:名詞も動詞も辞書化、同名リレーでクリーンに更新
[url=https://www.geogebra.org/m/twxxx3yq#material/aty4xugy]このワークシートは[/url][url=https://www.geogebra.org/m/twxxx3yq]Math by Code[/url]の一部です。[br][br]フラクタルなコンポジット構造を探索するビジター[br][br]コンポジットは名前をつける、変える、木構造を表示するなどの共通規格書の子として、ノード(フォルダ)とリーフ(ファイル)をぶら下げらえる。すると、ファイル構造のユーザは同じAPIでファイル構造を変えていけたね。[br][br]ビジターはお客様ではなく、特別の使命を託された工作員だった。コンポジットはフラクタル構造なので、情報を得るためのAPIとしてAcceptをつけて、工作員が来た時の行動を決めておくことで調査に協力できるようにしていた。[br][br]そんな2種類のデザインパタン、コンポジットとビジターは、FP化によってどうなるのだろう。[br]
[b][size=150]<OOP:Composite>[/size][/b][br]まずCompositeのOOP版です。[br][size=150]<ファイル探索・操作の実装>[br][/size]# ==========================================[br]# 【共通規格】ファイルもフォルダも等価に扱うための抽象クラス[br]# ==========================================[br]class FileSystemNode:[br] """【要素】ノード(フォルダ)でもリーフ(ファイル)でも同じ方法で扱える共通仕様"""[br] def __init__(self, name):[br] [url=http://self.name/]self.name[/url] = name[br][br] def getName(self):[br] return [url=http://self.name/]self.name[/url][br] def reName(self, new_name):[br] [url=http://self.name/]self.name[/url] = new_name[br][br] def add(self, node):[br] raise NotImplementedError("端末要素(ファイル)の下に、要素を追加することはできません。")[br][br] def remove(self, node):[br] raise NotImplementedError("端末要素(ファイル)から、要素を削除することはできません。")[br][br] def show_tree(self, depth=0):[br] """ツリー構造を画面に表示するための共通メソッド"""[br] raise NotImplementedError()[br][br][br]# ==========================================[br]# 【末端(リーフ)】中身が「ファイル」のクラス[br]# ==========================================[br]class FileLeaf(FileSystemNode):[br] """<末端>を表す具体的なクラス"""[br] def __init__(self, name, size):[br] super().__init__(name)[br] self.size = size # ファイル固有の属性[br][br] def show_tree(self, depth=0):[br] # インデントをつけてファイル名を表示[br] print(" " * depth + f"・{self.getName()} ({self.size}KB)")[br][br]# ==========================================[br]# 【複合体(コンポジット)】中身が「フォルダ」のクラス[br]# ==========================================[br]class FolderComposite(FileSystemNode):[br] """<木>を表す具体的なクラス。自分の中にさらに<要素>の集合体を持つ"""[br] def __init__(self, name):[br] super().__init__(name)[br] self.children = [] # 【自己参照の受け皿】FileSystemNode(ファイルかフォルダ)を格納するリスト[br][br] def add(self, node: FileSystemNode):[br] """xsが末端でなければ、xを追加する"""[br] self.children.append(node)[br] return self # メソッドチェーン(連続追加)ができるように自分を返す[br][br] def remove(self, node: FileSystemNode):[br] """xsにxがあれば、xを取り除く(どの階層であっても、見つけたら削除)"""[br] if node in self.children:[br] self.children.remove(node)[br] return True[br] [br] # 自分の直下になければ、さらに下層のフォルダ(自己相似)に「xを持ってる?」と探索をお願いする[br] for child in self.children:[br] if isinstance(child, FolderComposite):[br] if child.remove(node):[br] return True[br] return False[br][br] def show_tree(self, depth=0):[br] """フォルダの中身を再帰的に全件走査して表示する(フラクタル展開)"""[br] print(" " * depth + f"□{self.getName()}/")[br] for child in self.children:[br] # 相手がファイルかフォルダかを一切気にせず、同じ「show_tree」を呼び出すだけ![br] child.show_tree(depth + 1)[br][br]# ==========================================[br]# 【実行・ユーザー側のコード】部分と全体を一様に操作する[br]# ==========================================[br]print("コンポジットアルゴリズムスタート:\n")[br][br]# 1. PCでおなじみの木構造(ファイルシステム)を構築[br]root_dir = FolderComposite("デザインパタン")[br]gener_dir = FolderComposite("生成系")[br]behav_dir = FolderComposite("振る舞い系")[br][br]# 端末要素(ファイル)の作成[br]file_a = FileLeaf("f a c t o r y .py", 12)[br]file_b = FileLeaf("m e d i a t o r .py", 45)[br]file_c = FileLeaf("i t e r a t o r .py", 8)[br][br]# フォルダ構造へ追加(どの階層でも、上がxs、真下がxの関係)[br]root_dir.add(gener_dir).add(behav_dir)[br]gener_dir.add(file_a)[br]behav_dir.add(file_b).add(file_c)[br][br]print("--- 1. 最初に構築したツリー構造を表示 ---")[br]root_dir.show_tree()[br][br]print("\n--- 2. 名前の変更(reName)を『ファイル』と『フォルダ』に一様に適用 ---")[br]# ユーザーは相手がリーフかノードかを意識せず、全く同じメソッドを叩ける[br]gener_dir.reName("工場系") # フォルダの名前変更[br]file_c.reName("List Access Interface .py") # ファイルの名前変更[br]root_dir.show_tree()[br][br]print("\n--- 3. 要素の削除(remove)を発動 ---")[br]print(f"削除実行: {file_c.getName()} を消去します。")[br]root_dir.remove(file_c) # ルートフォルダに丸投げすれば、下層まで勝手に探して消してくれる[br]root_dir.show_tree()[br][OUT][br][br]コンポジットアルゴリズムスタート:[br]--- 1. 最初に構築したツリー構造を表示 ---[br]□デザインパタン/[br] □生成系/[br] ・f a c t o r y .py (12KB)[br] □振る舞い系/[br] ・m e d i a t o r .py (45KB)[br] ・i t e r a t o r .py (8KB)[br][br]--- 2. 名前の変更(reName)を『ファイル』と『フォルダ』に一様に適用 ---[br]□デザインパタン/[br] □工場系/[br] ・f a c t o r y .py (12KB)[br] □振る舞い系/[br] ・m e d i a t o r .py (45KB)[br] ・List Access Interface .py (8KB)[br][br]--- 3. 要素の削除(remove)を発動 ---[br]削除実行: List Access Interface .py を消去します。[br]□デザインパタン/[br] □工場系/[br] ・f a c t o r y .py (12KB)[br] □振る舞い系/[br] ・m e d i a t o r .py (45KB)
[b][size=150]<FP:Composite>[br][/size][/b][br][color=#0000ff][b][size=200]フラクタル構造、木構造の可能なオブジェクト[br]と言えば、辞書ですね。[br][/size][/b][/color][br]まずデータ(名詞)です。[br]ファイルは数値(サイズ)、フォルダdicは辞書という構造の辞書を初期値initial_treeで 作ります。[br][br]次は処理(動詞)です。[br]処理関数も辞書にして管理関数辞書fs_operatorsとします。この発想は今まで何度も使いましたね。[br]処理辞書は、管理名をキーにしたラムダ式にしましょう。[br]管理対象となるtreeに対する操作self_funcができます。☆は操作パラメータです。[br]"管理名": lambda self_func, tree,☆が1つのラムダ式の形式です。[br]例えば[br]管理名=renameなら、ツリー構造のキーkと値vに対して、[br]☆=old_name, new_nameとしましょう。[br]ツリーのキーkをみてold_nameならnew_nameとして、old_nameでないときはkのままを対象ノード[br]そのツリーノードに対して、[br]もしvがフォルダつまり、isinstance(v, dict)ならば[br]self_func(self_func, v, old_name, new_name)のように同じことをフォルダvに対して繰り返し、[br]そうでないなら、掘り下げないでファイルvを読み取りましょう。[br]管理名=removeなら、☆=target_nameとして、同様に作れますね。[br][br]ファイル構造initial_treeというデータと管理関数辞書fs_operatorsを投げるとフレームワークの親玉関数がファイル構造を新オブジェクトとしてリレーするというFP化が思いつきますね。[br][br]親玉関数execute_fsは動詞としての管理名op_typeを文字列で受け取り、その引数は*argsで可変で受け取ります。op = fs_operators[op_type]とすることで、opに処理関数が格納されます。[br]もう一つの親玉関数show_tree(tree: dict, depth=0)は、子分の関数がいなくても、isinstance(tree, dict)で[br]再帰的に進めます。[br][size=200][b][color=#0000ff][size=150]データ(名詞)も辞書、処理(動詞)も辞書。[br]FPは辞書だらけになります。[br][/size][/color][/b][/size][br]# Composite FP[br]# ==========================================[br]# 1. データ(名詞)の定義:完全な入れ子辞書[br]# ==========================================[br]initial_tree = {[br] "デザインパタン": {[br] "生成系": {[br] "factory.py": 12[br] },[br] "振る舞い系": {[br] "mediator.py": 45,[br] "iterator.py": 8[br] }[br] }[br]}[br][b]# ==========================================[br]# 2. 処理(動詞)の定義:管理関数辞書(ラムダのパック)[br]# ==========================================[br]# フラクタル構造(木)を再帰的に巡り、元のデータを破壊せずに「新築」して返す関数群[br]fs_operators = {[br] "rename": lambda self_func, tree, old_name, new_name: {[br] (new_name if k == old_name else k): ([br] self_func(self_func, v, old_name, new_name) if isinstance(v, dict) else v[br] ) for k, v in tree.items()[br] }, [br] "remove": lambda self_func, tree, target_name: {[br] k: (self_func(self_func, v, target_name) if isinstance(v, dict) else v)[br] for k, v in tree.items() if k != target_name[br] }[br]}[br][/b][b][color=#0000ff]# ==========================================[br]# 3. フレームワーク(親玉関数):再帰の仲介[br]# ==========================================[br]def execute_fs(tree: dict, op_type: str, *args) -> dict:[br] """管理関数辞書から動詞を取り出し、再帰のトリガーを引く親玉関数"""[br] op = fs_operators[op_type][br] return op(op, tree, *args)[br]def show_tree(tree: dict, depth=0):[br] """木構造をきれいに画面に描画する純粋表示関数"""[br] for k, v in tree.items():[br] if isinstance(v, dict):[br] print(" " * depth + f"□{k}/")[br] show_tree(v, depth + 1)[br] else:[br] print(" " * depth + f"・{k} ({v}KB)")[br][/color][/b]# ==========================================[br]# 4. 実行(不変なツリーの歴史のバケツリレー)[br]# ==========================================[br]tree_v0 = initial_tree[br]print("--- 1. 最初に構築したツリー構造を表示 ---")[br]show_tree(tree_v0)[br]print("\n--- 2. 名前の変更(rename)を発動:『生成系』➔『工場系』へ ---")[br]# 元の tree_v0 は一切壊さず、新築された tree_v1 をリレーする[br]tree_v1 = execute_fs(tree_v0, "rename", "生成系", "工場系")[br]show_tree(tree_v1)[br]print("\n--- 3. 要素の削除(remove)を発動:『iterator.py』を消去 ---")[br]# tree_v1 からさらに新しい tree_v2 を新築[br]tree_v2 = execute_fs(tree_v1, "remove", "iterator.py")[br]show_tree(tree_v2)[br]print("\n【歴史の証明:ステートトラベル】")[br]print("イミュータブルな状態作成ではタイムトラベルができるはず。")[br]print(f"tree_v1の中に iterator.py は存在するか? ➔ {'iterator.py' in tree_v1['デザインパタン']['振る舞い系']}")[br][b][OUT][br]--- 1. 最初に構築したツリー構造を表示 ---[br]□デザインパタン/[br] □生成系/[br] ・factory.py (12KB)[br] □振る舞い系/[br] ・mediator.py (45KB)[br] ・iterator.py (8KB)[br]--- 2. 名前の変更(rename)を発動:『生成系』➔『工場系』へ ---[br]□デザインパタン/[br] □工場系/[br] ・factory.py (12KB)[br] □振る舞い系/[br] ・mediator.py (45KB)[br] ・iterator.py (8KB)[br]--- 3. 要素の削除(remove)を発動:『iterator.py』を消去 ---[br]□デザインパタン/[br] □工場系/[br] ・factory.py (12KB)[br] □振る舞い系/[br] ・mediator.py (45KB)[br]【歴史の証明:ステートトラベル】[br]イミュータブルな状態作成ではタイムトラベルができるはず。[br]tree_v1の中に iterator.py は存在するか? ➔ True[br][/b][br][b][color=#0000ff][size=150]op(op, tree, *args)[/size][/color][/b]というように、[br]自己参照文により、管理処理名と管理処理を同名して返します。[br]これは、「[url=https://www.geogebra.org/m/twxxx3yq#material/awtf6qjz][b][color=#0000ff]同名でnewして何が悪い[/color][/b][/url]」であった新オブジェクトの作成と同じロジックですね。[br]ラムダ式には名前がありません。self_funcはイミュータブルな世界では使い捨てになります。[br]外側に関数名を持たないラムダ式だからこそ、[br][b]自分自身(関数オブジェクト)opを同名の引数opとして次の階層へ新築リレー[/b]していくことで、[br]一切の破壊なしにフラクタルなフォルダの奥深くへと潜ることができるのですね。
[b][size=150]<OOP:Visitor>[/size][/b][br]つぎに、ファイルシステムというフラクタル構造に特命をもって全件捜索するVisitorのOOP版を[br]思い起こしましょう。[br]# ==========================================[br]# Composite構造+窓口[br]# ==========================================[br]class FileSystemNode:[br] """共通規格:名前の管理と、工作員を受け入れる窓口(accept)を定義"""[br] def __init__(self, name):[br] [url=http://self.name/]self.name[/url] = name[br] def getName(self):[br] return [url=http://self.name/]self.name[/url][br] def accept(self, agent):[br] raise NotImplementedError()[br]class FileLeaf(FileSystemNode):[br] """[末端] を表すファイルクラス"""[br] def __init__(self, name, size):[br] super().__init__(name)[br] self.size = size[br] def accept(self, agent):[br] # 工作員がファイルに接触した瞬間、自身のデータを工作員に調べさせる[br] # 戻り値として、自身のファイルサイズを親(フォルダ)に返す[br] return agent.visit_file(self)[br]class FolderComposite(FileSystemNode):[br] """[木] を表すフォルダクラス"""[br] def __init__(self, name):[br] super().__init__(name)[br] self.children = [][br] def add(self, node):[br] self.children.append(node)[br] return self[br] def accept(self, agent):[br] # 1. まずはフォルダに入ったことを工作員に通知する(行きがけの処理)[br] agent.visit_folder(self)[br] # 2. [再帰的巡回] フォルダの中にある子どもたちへ、工作員を次々と突入させる[br] # 各子どもたちが持っているサイズをボトムアップで回収し、合計(マージ)していく[br] total_sub_size = 0[br] for child in self.children:[br] total_sub_size += child.accept(agent) [br] # 3. すべての子どもの調査が終わったら、工作員にそのフォルダの最終報告をさせる(帰りがけの処理)[br] # このフォルダの合計サイズを、さらに上位の親フォルダへと返していく[br] return agent.visit_folder_post(self, total_sub_size)[br]# ==========================================[br]# 【工作員の共通規格】共通のインターフェース[br]# ==========================================[br]class AgentInterface:[br] """すべての特命工作員が必ず持つ能力(メソッド)"""[br] def visit_file(self, file_node): raise NotImplementedError()[br] def visit_folder(self, folder_node): raise NotImplementedError()[br] def visit_folder_post(self, folder_node, sub_size): raise NotImplementedError()[br] def show_result(self): raise NotImplementedError()[br]# ==========================================[br]# 【特命工作員A】フォルダごとに合計サイズを計算して表示する任務(修正版)[br]# ==========================================[br]class SizeAggregationAgent(AgentInterface):[br] """工作員A:ボトムアップで各フォルダの「中身すべての合計サイズ」を暴く職人"""[br] def __init__(self):[br] # 各フォルダの最終的な集計結果を記録する辞書[br] self.results = {}[br] def visit_folder(self, folder_node):[br] # 行きがけ:特に何もする必要はありません(通過通知のみ)[br] pass[br] def visit_file(self, file_node):[br] # ファイルを発見:そのファイルのサイズをそのまま上に返す[br] return file_node.size[br] def visit_folder_post(self, folder_node, sub_size):[br] # 帰りがけ:子どもたちの合計サイズを記録し、さらに上の親フォルダへとそのサイズを引き渡す[br] self.results[folder_node.getName()] = sub_size[br] return sub_size[br] def show_result(self):[br] # 最後に、蓄積されたすべてのフォルダの集計結果を報告[br] print(" --- フォルダサイズの集計 ---")[br] for folder_name, total_size in self.results.items():[br] print(f" □ {folder_name} 内の合計: {total_size} KB")[br]# ==========================================[br]# 【特命工作員B】サイズの大きい順にファイルランキングを表示する任務[br]# ==========================================[br]class SizeRankingAgent(AgentInterface):[br] """工作員B:森全体をくまなくスキャンし、巨大なファイルの上位ランキングを抽出する"""[br] def __init__(self):[br] self.file_list = [] # 発見したすべての(ファイル名, サイズ)を記録する[br] def visit_folder(self, folder_node):[br] pass[br] def visit_file(self, file_node):[br] # ファイルを見つけたら、名前とサイズをメモに記録してサイズを上に返す[br] self.file_list.append((file_node.getName(), file_node.size))[br] return file_node.size[br] def visit_folder_post(self, folder_node, sub_size):[br] # ランキング班は、合計サイズを上の親に引き渡す案内係だけを行う[br] return sub_size[br] def show_result(self):[br] # メモに集まったファイルを、サイズが大きい順(降順)にソーティング[br] ranking = sorted(self.file_list, key=lambda x: x[1], reverse=True)[br] print(" --- ファイルサイズでかいTOP3 ---")[br] for idx, (name, size) in enumerate(ranking[:3], 1):[br] print(f" No.{idx}: {name} ({size} KB)")[br]# ==========================================[br]# 【作動テスト】コンポジットの森に、工作員を順次投入[br]# ==========================================[br]print("=== Visitor Agent Algorithm: Start ===\n")[br]# 1. コンポジットの森の構築[br]root_dir = FolderComposite("デザインパタン")[br]gener_dir = FolderComposite("工場")[br]behav_dir = FolderComposite("行動")[br]file_a = FileLeaf("f a c t o r y .py", 12)[br]file_b = FileLeaf("m e d i a t o r .py", 45)[br]file_c = FileLeaf("i t e r a t o r .py", 8)[br]file_af = FileLeaf("f a c t o r y Fat .py", 123)[br]file_bf = FileLeaf("m e d i a t o r Fat .py", 456)[br]file_cf = FileLeaf("i t e r a t o r Fat .py", 89)[br]root_dir.add(gener_dir).add(behav_dir)[br]gener_dir.add(file_a).add(file_af)[br]behav_dir.add(file_b).add(file_c).add(file_bf).add(file_cf)[br]print("[特命 1] ")[br]agent_A = SizeAggregationAgent()[br]root_dir.accept(agent_A) # ルートから突入agent_A.show_result() # 結果報告[br]print("\n" + "="*50 + "\n")[br]print("[特命 2]")[br]agent_B = SizeRankingAgent()[br]root_dir.accept(agent_B) # ルートから突入[br]agent_B.show_result() # 結果報告[br][OUT][br]=== Visitor Agent Algorithm: Start ===[br][br][特命 1] [br] --- フォルダサイズの集計 ---[br] □ 工場 内の合計: 135 KB[br] □ 行動 内の合計: 598 KB[br] □ デザインパタン 内の合計: 733 KB[br][br]==================================================[br][br][特命 2][br] --- ファイルサイズでかいTOP3 ---[br] No.1: m e d i a t o r Fat .py (456 KB)[br] No.2: f a c t o r y Fat .py (123 KB)[br] No.3: i t e r a t o r Fat .py (89 KB)[br]
[b][size=150]<FP:Visitor>[br][/size][/b][br]ファイルシステムの状態管理自体が関数辞書で実現できてました。[br]だから、特命工作員Visitorも子クラスではなく、[br]ただのバラバラの関数にしてそれをまたグループ化して辞書にしてファイルシステムに送り込めば、[br]それぞれが目的達成してくれそうですね。[br][br]まず、ファイルシステムをroot_treeという名前の辞書データにしよう。[br]次に特命工作員A自体も辞書agent_A_ops にします。[br]ファイルアクセス時は、name, sizeからタプル(size,{})を返すラムダ式で処理します。[br]フォルダ帰りがけは、name, total_size, children_dictsからtotal_sizeを返すラムダ式ですが、[br]**children_dictsというスプレッドしてnameを変えて再帰的にたし上げたtotal_sizeを返します。[br]次に特命工作員Bは辞書agent_B_ops にします。[br]ファイルアクセス時は、name, sizeから(size,[(name,size)])を返すラムダ式で処理します。フォルダ帰りがけは、name, total_size, children_listsからtotal_sizeと集まったリストchildren_lists上に丸投げします。[br][br]親玉関数はvisit_fsはフォルダ名と中身を同名リレーでイミュータブルな世界で工作員agentで調査します。[br]引数はself_func, dir_nameの文字列, sub_treeの辞書, agentで、返り値は(サイズ、レコード)のタプルです。その処理手順は、次のようにしましょう。[br]current_total_size = 0で先頭で初期化します。記録メモaccumulated_record は工作員の種別に応じて初期化します。[br]このあとはfor文でループすればよいですね。ツリーのキーk、値vに対して、[br][br]ファルダアクセス時はself_func(self_func, k, v, agent)でリレーして潜りながら、タプルを取得しては、[br](child_size, child_record)から、current_total_sizeに child_sizeを加算したあと、[br]帰りがけにはフォルダなら記録メモaccumulated_record もchild_recordもフラットに展開して合体したものを、同名の新記録メモとしますが、ファイルならば、child_recordを追加します。[br][br]ファイルアクセス時は工作員のファイルアクセス時のタプル(child_size, child_record)を取得して、サイズchild_sizeを加算します。記録メモが工作員Bが使うリストの場合は記録child_recordの追加もしましょう。[br]この階層の調査の最後にはフォルダの帰りがけ処理をして返すことで、上向きリレーが続きますね。[br][br]# Visitor FP[br]# ==========================================[br]# 1. 対象データ(名詞):完全な入れ子辞書(Composite構造)[br]# ==========================================[br]root_tree = {[br] "デザインパタン": {[br] "工場": {[br] "factory.py": 12,[br] "factoryFat.py": 123[br] },[br] "行動": {[br] "mediator.py": 45,[br] "iterator.py": 8,[br] "mediatorFat.py": 456,[br] "iteratorFat.py": 89[br] }[br] }[br]}[br][br]# ==========================================[br]# 2. 特命工作員(動詞)の定義:作戦関数をパックした辞書[br]# ==========================================[br][br]# 【特命 1】各フォルダの合計サイズを集計・記録する作戦辞書[br]agent_A_ops = {[br] # ファイル接触時:(自身のサイズ, 下位のフォルダ記録辞書)[br] "file_op": lambda name, size: (size, {}), [br] # フォルダの帰りがけ:(このフォルダの合計サイズ, 統合された記録辞書)[br] "folder_post_op": lambda name, total_size, children_dicts: ([br] total_size,[br] {**children_dicts, name: total_size} # 新しい歴史をマージして上に投げる[br] )[br]}[br][br]# 【特命 2】ファイルサイズでかいリストを上へと回収する作戦辞書[br]agent_B_ops = {[br] # ファイル接触時:(自身のサイズ, 自身のペアを包んだリスト)[br] "file_op": lambda name, size: (size, [(name, size)]),[br] # フォルダの帰りがけ:(このフォルダの合計サイズ, 統合されたフラットなリスト)[br] "folder_post_op": lambda name, total_size, children_lists: ([br] total_size,[br] children_lists # 集まったリストをそのまま上に投げる[br] )[br]}[br][br]# ==========================================[br]# 3. フレームワーク(親玉関数):フォルダ名と中身を同名リレー[br]# ==========================================[br]def visit_fs(self_func, dir_name: str, sub_tree: dict, agent: dict) -> tuple:[br] """[br] 【Visitor親玉関数】[br] 「現在のフォルダ名」と「その中身の辞書」を正しく見つめながら、一切の破壊なしに潜る。[br] """[br] current_total_size = 0[br] # 工作員のタイプ(特命1なら辞書、特命2ならリスト)に合わせて初期の記録用バケツを新築[br] accumulated_record = {} if agent == agent_A_ops else [][br][br] for k, v in sub_tree.items():[br] if isinstance(v, dict):[br] # フォルダを発見:自分自身(self_func)、フォルダ名(k)、その中身(v)を同名リレー![br] child_size, child_record = self_func(self_func, k, v, agent)[br] current_total_size += child_size[br] [br] # 帰ってきた下層の歴史(オブジェクト)を、元のデータを壊さずに統合[br] if isinstance(accumulated_record, dict):[br] accumulated_record = {**accumulated_record, **child_record}[br] else:[br] accumulated_record = accumulated_record + child_record[br] else:[br] # ファイルを発見:工作員のファイル作戦オブジェクトを発動[br] child_size, child_record = agent["file_op"](k, v)[br] current_total_size += child_size[br] [br] if isinstance(accumulated_record, list):[br] accumulated_record = accumulated_record + child_record[br][br] # この階層の走査がすべて終わったら(帰りがけ)、工作員の最終報告処理を叩いて上にリレー![br] return agent["folder_post_op"](dir_name, current_total_size, accumulated_record)[br][br]# ==========================================[br]# 4. 実行(工作員たちの任務遂行)[br]# ==========================================[br]print("=== Visitor Agent Algorithm: Start ===\n")[br][br]print("[特命 1] ")[br]# ルートのフォルダ名「"Root"」の代わりに、最上位の辞書をバラして外側から起動[br]root_name = list(root_tree.keys())[0][br]root_content = root_tree[root_name][br]# 同名リレーのトリガーを引く[br]_, total_folder_sizes = visit_fs(visit_fs, root_name, root_content, agent_A_ops)[br]print(" --- フォルダサイズの集計 ---")[br]for folder_name, total_size in total_folder_sizes.items():[br] print(f" □ {folder_name} 内の合計: {total_size} KB")[br]print("\n" + "="*50 + "\n")[br][br]print("[特命 2]")[br]_, all_files_flat = visit_fs(visit_fs, root_name, root_content, agent_B_ops)[br]# 帰ってきた「クリーンに新築されたフラットなリスト」をサイズ順にソートして3つ切り出す[br]ranking = sorted(all_files_flat, key=lambda x: x[1], reverse=True)[br]print(" --- ファイルサイズでかいTOP3 ---")[br]for idx, (name, size) in enumerate(ranking[:3], 1):[br] print(f" No.{idx}: {name} ({size} KB)")[br]
[b][size=150]<振り返り>[/size][/b][br][br]コンポジットパターン[br]名詞initial_treeと動詞fs_operatorsを辞書にした。[br]フレームワークexecute_fsの中でself_funcを[br]同名リレーop(op, tree, *args)をした。[br][br]ビルダパターン[br]名詞root_treeと動詞agent_A_ops、agent_B_opsを辞書にした。[br]フレームワークvisit_fsの中で、self_funcを[br]同名リレーself_func(self_func, k, v, agent)をした。[br][br]相似形の設計だね。[br][br][b][color=#0000ff][size=150]名詞も動詞も辞書になり、[br]同名の新規オブジェクトの連続作成によるリレー、[br]同名リレーで記録メモをクリーンに更新できたね。[br][/size][/color][/b][br]同名上書きや、同名自己参照という一見裏技に見える新オブジェクト名の意図的な生成が、[br][br]名詞と動詞の辞書たちの安全を守っていたんだね。[br][br][b][u][size=150][color=#9900ff]課題:geogaraの再帰パタンはどんな特徴があるかを調べよう。[br][/color][/size][/u][/b][br]再帰構造、再帰関数はgeogebraでどうなっているのだろうか。[br]再帰関数といえば、リカージョンの構造は表だってありません。[br]代わりに反復コマンドという意味あいの[br]Iterationコマンドがあります。[br]f(x) = x^2として、[br]Iteration(f, 3,2)で、(3^2)^2=81が求められるわけです。[br]電卓の代わりですね。[br]また、IterationListコマンドがありました。[br][url=https://www.geogebra.org/m/twxxx3yq#material/zu9y3qrc]fibをやろう[/url]でも取り組んだように、[br][b]数列生成のための漸化式コマンドです。[/b][br]IterationList(a+b,a,b,{1,1},30)でフィボナッチ数列が、[br]terationList(a+b,a,b,{2,1},30)でリュカ数列が、[br]IterationList(a+b+c,a,b,c,{0,0,1},30)でトリボナッチ数列数列が、[br]Sequence(fibo(k+1)/fibo(k),k, 1,20)で黄金比の数列が、[br]カンタンに生成できた。[br][br]また、lst=Sequence(100)をつくり、[br]b=Product(lst)で100!がこの2行で計算できてしまう。[br][br]このgeobebraの軽快さは、電卓代わり程度という割り切りが必要だ。[br][br]自分は使ったことがなかったが、[br]さすがに[b]図形代数、Geogebraならでは[/b]の、数列ならぬ[b]点列生成[/b]もできてしまう。[br][i]A[/i] = (0,0) [br][i]B[/i] = (8,0)[br][code]IterationList(Midpoint(A, C), C, {B}, 3)[br][/code][i]C [/i]0 => [i]B= (8,0)[/i][br][i]C [/i]1 = >[i]Midpoint[/i] ( [i]A[/i] , [i]C [/i]0 )=(4,0)[br][i]C [/i]2 = >[i]Midpoint[/i] ( [i]A[/i] , [i]C [/i]1 )=(2,0)[br][i]C [/i]3 => [i]Midpoint[/i] ( [i]A[/i] , [i]C [/i]2 ) =(1,0)[br]ができるはず。[br][br]再帰とえば、ファイル構造ではなく、フラクタルそのもの、[br]フラクタル図形がある。[br][br]タイトルは「フラクタルはバケツリレー」[br]シェルピンスキーのギャスケットを作りたければ、[br]A=(0,0)[br]B=(1,0)[br]Polygon(A,B,3)で決まる点がC[br]H={A,B,C}[br]D=Midpoint(B,C)[br]E=Midpoint(C,A)[br]F=Midpoint(A,B)[br]中点はよいとしてもDilateという相似縮小コマンドを探して、対称性を利用して、縮小の視点を[br]リストから探して、順次縮小三角形を作っていくことになる。[br]変数は重複したり、For文が使えないので、[br]定数のような順次縮小の命令を並べるのがせいぜいだ。[br]S1=SequencePolygon(Dilate(D,((1)/(2)),Element(H,k)),Dilate(E,((1)/(2)),Element(H,k)),Dilate(F,((1)/(2)),Element(H,k))),k,1,3)[br]S2=SequenceDilate(S1,((1)/(2)),Element(H,k)),k,1,3)[br]S3=SequenceDilate(S2,((1)/(2)),Element(H,k)),k,1,3)[br]S4=SequenceDilate(S3,((1)/(2)),Element(H,k)),k,1,3)[br]S5=SequenceDilate(S4,((1)/(2)),Element(H,k)),k,1,3)[br]S6=SequenceDilate(S5,((1)/(2)),Element(H,k)),k,1,3)[br][br]それでも、[b]副作用のないバケツリレー[/b]をしているといえばしている。[br]もとの点は残しているのだから。