<?xml version="1.0" encoding="UTF-8"?><rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>SHIINAYANE (ja)</title><description>YK&apos;s Blog</description><link>https://www.shiinayane.com/</link><language>ja</language><item><title>AIワークフローをコードにした翌日、自分で壊した</title><link>https://www.shiinayane.com/ja/posts/i-turned-my-ai-workflow-into-code-then-i-tore-it-apart/</link><guid isPermaLink="true">https://www.shiinayane.com/ja/posts/i-turned-my-ai-workflow-into-code-then-i-tore-it-apart/</guid><description>「AIワークフローをコードにしてみた」を公開した翌日、BiliKitのM5.0がその前提を崩した。</description><pubDate>Mon, 27 Jul 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;7月25日、&lt;a href=&quot;/ja/posts/i-turned-my-ai-workflow-into-code/&quot;&gt;「AIワークフローをコードにしてみた」&lt;/a&gt;という記事を公開した。&lt;/p&gt;
&lt;p&gt;その翌日、記事の前提を自分でひっくり返した。&lt;/p&gt;
&lt;p&gt;あまりに早くて、少し笑ってしまう。記事を書き終えた時点では、BiliKitを作る中で増えていった工程管理がようやく安定したと思っていた。リスク分類、品質ゲート、独立レビュー、複雑さの上限。それらを再利用可能なCodex Skillにもまとめた。&lt;/p&gt;
&lt;p&gt;ところが翌日、BiliKit自身が反例になった。&lt;/p&gt;
&lt;h2&gt;どんどん完成していったスクロール位置&lt;/h2&gt;
&lt;p&gt;作業していたのはM5.0だった。要件自体は単純だ。&lt;/p&gt;
&lt;p&gt;一覧から動画を開き、再生画面から戻ったとき、入る前に見ていた場所へできるだけ戻す。&lt;/p&gt;
&lt;p&gt;当時のBiliKitは独自のルーティングを使っていた。再生画面を開くと一覧Viewがツリーから外れ、戻ると作り直される。そのため、一覧の内容もスクロール位置も手動で復元する必要があった。&lt;/p&gt;
&lt;p&gt;最初は選択した動画IDだけを保存した。これでカードを画面内へ戻すことはできたが、離れる前のviewportまでは戻らない。&lt;/p&gt;
&lt;p&gt;そこで生のoffsetも記録するようにした。&lt;/p&gt;
&lt;p&gt;その後に出てきた問題は、どれも筋が通っていた。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;コンテンツの高さが変わると、以前のoffsetへ到達できないことがある。&lt;/li&gt;
&lt;li&gt;古い&lt;code&gt;ScrollView&lt;/code&gt;のcallbackが新しいsnapshotを上書きする可能性がある。&lt;/li&gt;
&lt;li&gt;非同期処理の遅い結果にはrequest identityとgenerationの確認が必要になる。&lt;/li&gt;
&lt;li&gt;ユーザーが自分でスクロールしたら、プログラム側の復元状態を解除しなければならない。&lt;/li&gt;
&lt;li&gt;戻った画面が本当に離れる前と同じか、決定的テストとXCUIでも確認したい。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;問題を一つ直すたび、実装は完成に近づいていった。&lt;/p&gt;
&lt;p&gt;テストも通っていた。&lt;/p&gt;
&lt;p&gt;今振り返ると、そこがいちばん厄介だった。テストもレビューも品質ゲートも、復元処理が正しく書けているかを確認していた。次の疑問は残ったままだった。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;そもそも、なぜ一覧画面を破棄する必要があるのか。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;いったん作業を止め、小さなSwiftUIのprobeを作った。条件分岐によるViewの置き換え、独自の復元、標準の&lt;code&gt;NavigationStack&lt;/code&gt;を並べて比較した。&lt;/p&gt;
&lt;p&gt;結果は分かりやすかった。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;NavigationStack&lt;/code&gt;ではroot Viewが作り直されず、元のstate identityも維持された。苦労して解いていた復元問題のかなりの部分は、自分たちで選んだナビゲーション構造から生まれていた。&lt;/p&gt;
&lt;p&gt;そこでBiliKitは標準の&lt;code&gt;TabView(.sidebarAdaptable)&lt;/code&gt;へ移行し、各Tabがそれぞれ&lt;code&gt;NavigationStack(path:)&lt;/code&gt;を持つ構成に変えた。独自の&lt;code&gt;AppRoute&lt;/code&gt;と&lt;code&gt;AppReturnSnapshot&lt;/code&gt;は削除した。一方、上限付きの一覧workset、requestのキャンセル、プレイヤーのライフサイクル管理は残した。スクロール位置はSwiftUIの意味的なIDに任せている。&lt;/p&gt;
&lt;p&gt;実際の人気動画一覧で2回往復した結果は次の通りだった。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;0.195066 → 0.194867
0.389877 → 0.389447
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;ViewModelが生の数値offsetを保存し、clamp、復元latch、古いcallbackの分離まで抱える必要はなくなった。&lt;/p&gt;
&lt;h2&gt;プロセスはきちんと動いていた&lt;/h2&gt;
&lt;p&gt;今回、明らかに手を抜いたAgentがいたわけではない。&lt;/p&gt;
&lt;p&gt;リポジトリの規約は読まれていた。リスクも拾えていた。検証レベルの選択にも大きな問題はなかった。古いcallbackによる汚染、到達不能なoffset、ライフサイクルの後始末、XCUIの操作など、実際の不具合も見つけている。&lt;/p&gt;
&lt;p&gt;すべての工程が、同じ前提を受け入れていた。現在のナビゲーション構造を維持したまま、状態復元を正しく実装するという前提だ。&lt;/p&gt;
&lt;p&gt;それがtask contractに入ると、後続のAgentはその方針に沿って非常にうまく作業する。&lt;/p&gt;
&lt;p&gt;Reviewerは境界を確認し、red reviewerは失敗経路を探す。テストは増え、文書は詳しくなる。各工程が現在の案を正しくすると同時に、このまま進むのが当然のように見せていた。&lt;/p&gt;
&lt;p&gt;前の記事で、&lt;code&gt;project-governance-bootstrap&lt;/code&gt;を小さなコンパイラのようなものだと書いた。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;リポジトリの事実
+ アーキテクチャ文書
+ テスト
+ セキュリティ境界
→ プロジェクトのルール
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;今は、この例えには大きな穴があると思っている。&lt;/p&gt;
&lt;p&gt;コンパイラが扱えるのは、入力されたものだけだ。&lt;/p&gt;
&lt;p&gt;アーキテクチャ、task contract、既存実装のすべてがより単純な案を見落としていれば、Skillは今の案をさらに厳密にすることしかできない。経路依存は消えず、読みやすい文書、追加のチェック、強い慣性へ変わっていく。&lt;/p&gt;
&lt;h2&gt;修正まで別のプロセスにしかけた&lt;/h2&gt;
&lt;p&gt;問題に気づいた後も、最初に考えたのは今回の経験をルールにすることだった。&lt;/p&gt;
&lt;p&gt;高リスク作業では先に代替案を列挙する。challengerを必須にする。decision ledgerを作る。問題設定を確認するreviewerを追加する。&lt;/p&gt;
&lt;p&gt;書いている途中で、また違う気がしてきた。&lt;/p&gt;
&lt;p&gt;プロセスが増えすぎるのを防ぐためにプロセスを増やしていた。Agentがtask contractを機械的に実行しないよう、さらに長いtask contractを作ろうとしていた。&lt;/p&gt;
&lt;p&gt;さっき壊したものと、それほど変わらない。&lt;/p&gt;
&lt;p&gt;今回はworkflow v2を作らなかった。&lt;/p&gt;
&lt;p&gt;BiliKitの共同作業ルールをそのまま軽くした。一つのcommitで設定と文書を766行削除し、固定のAgent定義も5つ消した。現在の&lt;code&gt;AGENTS.md&lt;/code&gt;に残っているのは、プロジェクトの事実、アーキテクチャ境界、安全上の制約、実際に使える検証コマンド、commitの規約だ。&lt;/p&gt;
&lt;p&gt;品質チェックは残っている。&lt;/p&gt;
&lt;p&gt;SwiftPM、&lt;code&gt;xcodebuild&lt;/code&gt;、ライフサイクルテスト、セキュリティ境界のチェックには、今も答えられる問いがある。ビルドが壊れていないか、キャンセルが正しいか、リソースが解放されるか、実際のAppが動くか。&lt;/p&gt;
&lt;p&gt;固定のリスク色、task contractの書式、reviewer chain、複雑さの予算は、毎回行う儀式から外した。&lt;/p&gt;
&lt;p&gt;本当に必要な変更では使える。すべての作業で工程を守った証明から始める必要はない。&lt;/p&gt;
&lt;h2&gt;二つのSkillsをどうするか&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;apple-dev-loop&lt;/code&gt;は、今のところ大きく変えなくてもよさそうだ。&lt;/p&gt;
&lt;p&gt;Appleプラットフォームで検証方法を選び、実行する。SwiftPMで十分な場面、&lt;code&gt;xcodebuild&lt;/code&gt;が必要な場面、&lt;code&gt;.xcresult&lt;/code&gt;、署名済みApp、XCUI、Instrumentsまで必要な場面を切り分ける。&lt;/p&gt;
&lt;p&gt;担当範囲は比較的はっきりしている。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;project-governance-bootstrap&lt;/code&gt;は事情が違う。&lt;/p&gt;
&lt;p&gt;これはリポジトリから作業ルールを作るためのSkillだった。十分な情報を読めば、そのプロジェクトに合ったワークフローを生成できるという前提が含まれている。&lt;/p&gt;
&lt;p&gt;今回のBiliKitは、リポジトリ自身が間違った方針を詳しく説明していることもあると示した。&lt;/p&gt;
&lt;p&gt;このSkillはもう一度考え直す。既存の事実と検証入口を整理するだけの道具まで縮めるかもしれないし、そのままarchiveするかもしれない。まだ決めていない。&lt;/p&gt;
&lt;h2&gt;新しい答えはまだない&lt;/h2&gt;
&lt;p&gt;現在の主要なソースとテストのディレクトリを数えると、BiliKitのSwiftは約2万5,800行ある。&lt;/p&gt;
&lt;p&gt;プロジェクトが大きくなれば、一つのpromptと一度のdiff reviewだけでは足りない。以前のルールも無駄だったわけではなく、実際に多くの問題を見つけてくれた。&lt;/p&gt;
&lt;p&gt;ただ、その経験を汎用ワークフローにまとめ、次のプロジェクトで進む方向まで自動的に選べるとは思わなくなった。&lt;/p&gt;
&lt;p&gt;少なくとも今回はできなかった。&lt;/p&gt;
&lt;p&gt;7月25日の記事は残す。あの時点で本当にそう考えていた。消したり、最初から分かっていたように書き換えたりしても面白くない。&lt;/p&gt;
&lt;p&gt;この記事は、その翌日に起きたことの記録だ。&lt;/p&gt;
&lt;p&gt;この先は、またプロジェクトにやり方を変えさせられたときに考える。&lt;/p&gt;
</content:encoded></item><item><title>AI ワークフローをコードにしてみた</title><link>https://www.shiinayane.com/ja/posts/i-turned-my-ai-workflow-into-code/</link><guid isPermaLink="true">https://www.shiinayane.com/ja/posts/i-turned-my-ai-workflow-into-code/</guid><description>BiliKitはV1完成前に2.1万行を超えた。そこで身についた作業手順を、再利用できる2つのCodex Skillとして切り出した。</description><pubDate>Sat, 25 Jul 2026 00:00:00 GMT</pubDate><content:encoded>&lt;blockquote&gt;
&lt;p&gt;**2026年7月27日追記：**この記事を公開した翌日、BiliKitのM5.0でこのワークフロー最大の問題が表に出た。選んだ方針を厳密に実装する力はあっても、そもそもその問題をその方針で解くべきかは確認されないまま進んでしまう。元の記事はそのまま残し、続編として&lt;a href=&quot;/ja/posts/i-turned-my-ai-workflow-into-code-then-i-tore-it-apart/&quot;&gt;「AIワークフローをコードにした翌日、自分で壊した」&lt;/a&gt;を書いた。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;BiliKitはまだV1すら完成していないのに、Swiftのコードはすでに2.1万行を超えている。&lt;/p&gt;
&lt;p&gt;プロジェクトが小さかった頃は、AIに少し長めのプロンプトを渡し、出てきたdiffを自分で確認するだけでも何とかなっていた。間違いがあっても変更範囲が狭く、見つけるのはそれほど難しくなかった。&lt;/p&gt;
&lt;p&gt;ところが、コードが増えるにつれてこのやり方では足りなくなった。&lt;/p&gt;
&lt;p&gt;同じように「まずプロジェクトのルールを読んでから変更して」と頼んでも、セッションが変われば結果も変わる。テストは全部通っているのに、修正するレイヤーを間違えていることもあった。小さな不具合を直してほしかっただけなのに、新しい抽象化が一式増えて戻ってくることもある。&lt;/p&gt;
&lt;p&gt;BiliKitには、テストが通っただけでは判断しづらい箇所も多い。Keychain、プレイヤーのライフサイクル、並行処理のキャンセル、弾幕レンダリング、ローカルサーバーなどだ。ビルドや単体テストが成功しても、署名済みのAppや実際の操作で問題がないとは限らない。&lt;/p&gt;
&lt;p&gt;そのため、リポジトリには少しずつ次のような仕組みが増えていった。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;AGENTS.md&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;リスク分類&lt;/li&gt;
&lt;li&gt;品質ゲート&lt;/li&gt;
&lt;li&gt;独立レビュー&lt;/li&gt;
&lt;li&gt;複雑さの上限&lt;/li&gt;
&lt;li&gt;作業内容に応じた検証方法&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;しばらく使ううちに、BiliKit内のワークフローはだいたい安定した。&lt;/p&gt;
&lt;p&gt;そこで別のプロジェクトを始めて、また最初から作り直す必要があることに気づいた。&lt;/p&gt;
&lt;p&gt;ルールの一部は&lt;code&gt;AGENTS.md&lt;/code&gt;にあり、一部は品質ゲートの文書やスクリプトにあり、残りは自分の習慣としてしか残っていない。記憶を頼りに書き直すか、BiliKitのファイルをそのままコピーするしかない。&lt;/p&gt;
&lt;p&gt;前者は漏れが出るし、後者は余計なものまで持ち込んでしまう。&lt;/p&gt;
&lt;p&gt;静的サイトにBiliKitのプレイヤー、Keychain、ローカルサーバー、メディアリダイレクト向けルールは必要ない。一方で、1,000行未満の小さなツールでも、認証情報を扱うなら雑には変更できない。&lt;/p&gt;
&lt;p&gt;そこで、ルールそのものではなく、ルールを作る手順を再利用することにした。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;codex-engineering-skills&lt;/code&gt;というリポジトリを作り、まず2つのCodex Skillに分けた。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;project-governance-bootstrap&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;apple-dev-loop&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;前者はプロジェクトを読んで、そのプロジェクトに合った開発ルールを作る。後者はAppleプラットフォーム向けのビルド、テスト、検証を担当する。&lt;/p&gt;
&lt;h2&gt;BiliKitのAGENTS.mdをそのままテンプレートにはできなかった&lt;/h2&gt;
&lt;p&gt;最初はBiliKitの&lt;code&gt;AGENTS.md&lt;/code&gt;を少し一般化して、共通テンプレートにすればいいと思っていた。&lt;/p&gt;
&lt;p&gt;実際に作り始めると、すぐに無理が出た。&lt;/p&gt;
&lt;p&gt;BiliKitでは認証、リダイレクト、ローカルサーバー、再生処理、並行処理、レンダラー、破壊的なマイグレーションを高リスク領域として扱っている。BiliKitには実際にその失敗パターンがあるので、この分類で問題ない。&lt;/p&gt;
&lt;p&gt;同じ一覧を静的サイトに置けば、ただの形式になってしまう。コマンドラインパーサーの変更に、署名済みAppでのKeychain検証まで要求するようになれば、さすがにおかしい。&lt;/p&gt;
&lt;p&gt;そのため&lt;code&gt;project-governance-bootstrap&lt;/code&gt;は、まずリポジトリを読む。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;既存の開発ルール&lt;/li&gt;
&lt;li&gt;アーキテクチャ上の決定&lt;/li&gt;
&lt;li&gt;manifestと依存関係&lt;/li&gt;
&lt;li&gt;テスト&lt;/li&gt;
&lt;li&gt;CI&lt;/li&gt;
&lt;li&gt;セキュリティ境界&lt;/li&gt;
&lt;li&gt;リリース方法&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;その後、必要なガバナンスを大まかに3段階から選ぶ。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;light&lt;/strong&gt;：小規模で元に戻しやすく、主な検証方法が1つだけのもの&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;standard&lt;/strong&gt;：複数モジュール、公開境界、永続化、CI、複数のテスト層などがあるもの&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;critical&lt;/strong&gt;：認証情報、権限、破壊的な移行、信頼できない入力、ローカルサーバー、メディアのライフサイクル、本番インフラなどを扱うもの&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;コード行数は参考にしかならない。10万行の生成コードより、数十行のデータ削除処理のほうが危険なこともある。&lt;/p&gt;
&lt;p&gt;生成されるのは、あくまでそのプロジェクト用のルールだ。Skillとして再利用するのは、そこに至るまでの判断手順になる。&lt;/p&gt;
&lt;h2&gt;テンプレートより小さなコンパイラに近い&lt;/h2&gt;
&lt;p&gt;今は&lt;code&gt;project-governance-bootstrap&lt;/code&gt;を、小さなコンパイラのようなものだと考えている。&lt;/p&gt;
&lt;p&gt;入力はだいたい次のようになる。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;コード
+ manifest
+ アーキテクチャ文書
+ テスト
+ CI
+ セキュリティ境界
+ リリースルール
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;出力はこちらだ。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;情報源の優先順位
+ アーキテクチャ境界
+ リスク領域
+ 検証コマンド
+ 権限の制限
+ 必要に応じたレビュー担当
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;リポジトリ内にはテンプレートも置いているが、出発点にすぎない。プロジェクトに関係のない節は、そのまま削除する。&lt;/p&gt;
&lt;p&gt;テンプレートは、一度節ができるとなかなか消えない。「Security」という見出しがあれば、実際には特別なセキュリティ境界がなくても、人もAIも何かを書こうとする。見た目は立派でも、実際には誰も守らない文書ができあがる。&lt;/p&gt;
&lt;p&gt;すでに十分なテストコマンドがあるなら、形式を揃えるためだけに別のゲートスクリプトを作る必要はない。どこに置いても通用する一般論を追加しても、あまり役には立たない。&lt;/p&gt;
&lt;p&gt;必要な分だけあればいい。&lt;/p&gt;
&lt;h2&gt;2つのSkillに分けた理由&lt;/h2&gt;
&lt;p&gt;BiliKitで使っていたワークフローには、2つの問題が混ざっていた。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;このリポジトリでは何を守るべきか&lt;/li&gt;
&lt;li&gt;今回のAppleプラットフォーム向け変更をどう検証するか&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;BiliKitはAppleプラットフォームのプロジェクトなので、普段は同時に必要になる。それでも、再利用する段階で2つに分けた。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;project-governance-bootstrap&lt;/code&gt;はリポジトリのルールを担当する。署名済みApp、UIテスト、性能記録が必要だと判断することはあっても、Xcodeの操作手順までは抱え込まない。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;apple-dev-loop&lt;/code&gt;は実際の検証を担当する。SwiftPM、&lt;code&gt;xcodebuild&lt;/code&gt;、&lt;code&gt;.xcresult&lt;/code&gt;、XCUI、署名済みApp、Computer Use、Instrumentsをどこで使うかは知っているが、アプリのアーキテクチャやリスク分類は決めない。&lt;/p&gt;
&lt;p&gt;2つは組み合わせて使えるが、依存はしていない。&lt;/p&gt;
&lt;p&gt;前者はRust、TypeScript、文書中心のリポジトリにも使える。後者は、すでに開発ルールが整っているSwiftプロジェクトでも単独で使える。&lt;/p&gt;
&lt;p&gt;一緒に使う機会が多いからといって、1つにまとめる必要はなかった。&lt;/p&gt;
&lt;h2&gt;起動しない条件も必要だった&lt;/h2&gt;
&lt;p&gt;Skillを書いてみて、起動条件もかなり重要だと分かった。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;apple-dev-loop&lt;/code&gt;が「Swift」という単語だけで起動したら、文法についての質問でもXcodeの確認、schemeの探索、ビルドまで始めてしまう。厳密ではあるが、明らかにやりすぎだ。&lt;/p&gt;
&lt;p&gt;そこで、使う条件と使わない条件の両方を書いている。&lt;/p&gt;
&lt;p&gt;Appleのツールチェーン、署名、実機、UI、性能検証が必要なタスクでは一連のループを使う。ソースだけで答えられるSwiftの質問や、実行環境を必要としない小さなPackage変更では使わない。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;project-governance-bootstrap&lt;/code&gt;も同じだ。リポジトリに&lt;code&gt;AGENTS.md&lt;/code&gt;があるという理由だけで、プロジェクト全体のルールを作り直してはいけない。&lt;/p&gt;
&lt;p&gt;以前は説明を多めに渡しても特に問題ないと思っていた。実際には、余分な説明もコンテキストを使い、ツールの選び方に影響する。&lt;/p&gt;
&lt;p&gt;単純なPackageテストにInstrumentsの詳しい手順まで読み込ませると、必要もないのに性能計測が候補に入ってくる。&lt;/p&gt;
&lt;p&gt;情報が多いほど良いとは限らなかった。&lt;/p&gt;
&lt;h2&gt;SKILL.mdを大きくしすぎない&lt;/h2&gt;
&lt;p&gt;最初の案では、2つの&lt;code&gt;SKILL.md&lt;/code&gt;がかなり巨大になるところだった。&lt;/p&gt;
&lt;p&gt;ガバナンス側にはすべてのリスク分類、テンプレート、レビュー担当、Apple向け例外を入れ、Apple側にはXcode、XCTest、署名、Simulator、UI、Instrumentsの手順を全部入れようとしていた。&lt;/p&gt;
&lt;p&gt;今は短い基本手順だけを置き、必要な資料をその都度読むようにしている。&lt;/p&gt;
&lt;p&gt;AppleプロジェクトでなければApple向けルールは読まない。独立した担当が不要ならAgent routingも読まない。PackageテストだけならInstrumentsの手順も読み込まない。&lt;/p&gt;
&lt;p&gt;一般にはprogressive disclosureと呼ばれるやり方だが、コンテキストの依存関係を管理していると考えるほうが分かりやすい。&lt;/p&gt;
&lt;p&gt;あるツールの説明が詳しいほど、そのツールは選ばれやすくなる。今回の判断に関係しないなら、最初から読み込まないほうがいい。&lt;/p&gt;
&lt;h2&gt;Appleの検証をどこまで行うか&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;apple-dev-loop&lt;/code&gt;には、次のような検証の段階がある。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;ソースと静的な制約の確認&lt;/li&gt;
&lt;li&gt;SwiftPM、単体テスト、結合テスト&lt;/li&gt;
&lt;li&gt;&lt;code&gt;xcodebuild&lt;/code&gt;と構造化された&lt;code&gt;.xcresult&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Xcode上の診断と操作&lt;/li&gt;
&lt;li&gt;再現可能なXCUIテスト&lt;/li&gt;
&lt;li&gt;署名済みAppと実際のUI操作&lt;/li&gt;
&lt;li&gt;対象を絞った&lt;code&gt;xctrace&lt;/code&gt;またはInstrumentsの記録&lt;/li&gt;
&lt;li&gt;CI、実機マトリクス、独立レビュー&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;毎回最後まで進むわけではない。今回の結論を証明できる段階まで来たら、そこで止める。&lt;/p&gt;
&lt;p&gt;スクリーンショットは単体テストの代わりにならない。署名なしのビルドではKeychainへのアクセスを確認できない。Appが起動しただけではライフサイクルの問題が直ったとは言えない。1台のMacで取ったトレースだけで、すべての対応端末を確認したことにもならない。&lt;/p&gt;
&lt;p&gt;Skillには小さな補助スクリプトも入れている。時間のかかる処理を始める前に、リポジトリ、workspace、scheme、Xcode、Developer Directoryを記録するものや、数千行のコンソール出力ではなく&lt;code&gt;.xcresult&lt;/code&gt;の構造化データを読むものだ。&lt;/p&gt;
&lt;p&gt;shellでXcodeを作り直すつもりはない。自動化して曖昧さを減らせる、範囲の狭い処理だけをスクリプトにしている。&lt;/p&gt;
&lt;h2&gt;インストールスクリプトでも事故は起きる&lt;/h2&gt;
&lt;p&gt;このリポジトリには、CodexからSkillを見つけられるようにシンボリックリンクを作るスクリプトがある。&lt;/p&gt;
&lt;p&gt;リンク先がすでに正しければ何もしない。別のリンクや実体ディレクトリが置かれていれば、上書きを拒否する。&lt;/p&gt;
&lt;p&gt;小さな処理だが、既存のSkillを黙って置き換えるインストーラーは普通にデータを消せる。&lt;/p&gt;
&lt;p&gt;一度のビルドを通すためにグローバルな&lt;code&gt;xcode-select&lt;/code&gt;を変更するのも似ている。そのプロジェクトは直っても、次に別のプロジェクトを開いたとき、どのXcodeが選ばれているか分からなくなる。&lt;/p&gt;
&lt;p&gt;そのため、設定はできるだけ現在のタスク内に閉じるようにした。状態が曖昧なら止まり、オプションのツールがないだけなら、今回の検証に必要になるまでblockerにはしない。&lt;/p&gt;
&lt;h2&gt;曖昧なルールをそのまま残せなくなった&lt;/h2&gt;
&lt;p&gt;BiliKitでは、次の一文でもだいたい意味が通じていた。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;重要な変更には独立レビューが必要。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;再利用するSkillにすると、すぐに疑問が出てくる。何を重要とするのか。複数ファイルを変更しただけでレビューが必要なのか。レビュー担当にはどこまで情報を渡すのか。結果が食い違ったらどうするのか。&lt;/p&gt;
&lt;p&gt;結局、適用範囲を細かくした。&lt;/p&gt;
&lt;p&gt;緑の作業では機械的にレビューを追加しない。単純作業ではない黄色の変更には、必要に応じて独立した読み取り専用レビューを付ける。赤の変更では失敗経路、キャンセル、所有権、セキュリティ、後片付け、ロールバックを重点的に見る。同時に複雑さにも上限を設け、厳密さを理由に手順が増え続けないようにする。&lt;/p&gt;
&lt;p&gt;ほかのルールも同じだった。&lt;/p&gt;
&lt;p&gt;最上位の決定的なテストが下位のテストを含んでいるなら、「すべてのテストを実行する」と言って同じ内容を何度も走らせる必要はない。&lt;/p&gt;
&lt;p&gt;実機確認も、ローカルの決定的なテストでは今回の結論を証明できないときに使う。&lt;/p&gt;
&lt;p&gt;Xcode MCPを使う場合も、接続先のXcodeプロセス、ウィンドウ、workspace、schemeを先に確認しないと、別のプロジェクトに対して正常に操作できてしまう。&lt;/p&gt;
&lt;p&gt;プロジェクト内の文脈に頼っていたルールは、再利用する段階で条件を書き直す必要があった。&lt;/p&gt;
&lt;h2&gt;Skill自体もテストする&lt;/h2&gt;
&lt;p&gt;Skillsリポジトリにはvalidatorがあり、現在は次の項目を確認している。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;metadata&lt;/li&gt;
&lt;li&gt;内部リンク&lt;/li&gt;
&lt;li&gt;置換されていないプレースホルダー&lt;/li&gt;
&lt;li&gt;shell構文&lt;/li&gt;
&lt;li&gt;空白とフォーマット&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;インストール先の衝突は別のスクリプトで確認する。&lt;/p&gt;
&lt;p&gt;ただし、ここで分かるのはファイル構造が壊れていないことだけだ。&lt;/p&gt;
&lt;p&gt;metadataもリンクもshellも正しくても、Skillの判断がおかしいことはある。起動範囲が広すぎたり、生成するルールが重すぎたり、既存の制約を見落としたり、今回の結論を証明できない検証方法を選んだりする可能性は残る。&lt;/p&gt;
&lt;p&gt;次は別のプロジェクトで試す必要がある。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;project-governance-bootstrap&lt;/code&gt;は、小さくて元に戻しやすいツール、普通のマルチモジュールアプリ、本当にセキュリティやライフサイクル上のリスクがあるプロジェクトで試したい。出てくるルールはそれぞれ違うはずだ。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;apple-dev-loop&lt;/code&gt;は、Packageの変更ならPackageテスト、Xcodeプロジェクトなら&lt;code&gt;xcodebuild&lt;/code&gt;、Keychainなら署名済みApp、性能問題ならInstrumentsというところで正しく止まれるかを確認する。&lt;/p&gt;
&lt;p&gt;結局、プロジェクト名だけを置き換えたBiliKitのルールが出てくるなら、また直せばいい。&lt;/p&gt;
</content:encoded></item><item><title>AIでコードは安くなった。でも開発は楽にならなかった</title><link>https://www.shiinayane.com/ja/posts/ai-made-code-cheap/</link><guid isPermaLink="true">https://www.shiinayane.com/ja/posts/ai-made-code-cheap/</guid><description>BiliKitはv1前の段階でSwift 21,300行になった。promptを書いてテストを回し、diffを読むだけでは足りなくなったので、開発フローそのものを見直した。</description><pubDate>Fri, 24 Jul 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;BiliKitはまだv1にもなっていないが、Swiftのコードはすでに21,300行ある。&lt;/p&gt;
&lt;p&gt;内訳はプロダクションコードが約12,700行、テストが約8,400行で、Swiftファイルは125個。Bilibiliの閲覧と検索、WebのQRコードを使ったログイン、AVPlayerによるDASH動画の再生、字幕、再生時間に同期した弾幕のスケジューリングと描画までは動くようになった。&lt;/p&gt;
&lt;p&gt;UIはまだかなり直したいし、Macらしい操作も足りない。署名、公証、リリース周り、v1向けの最終回帰テストも残っている。&lt;/p&gt;
&lt;p&gt;この規模になって、最初に使っていたAI開発のやり方が通用しなくなった。&lt;/p&gt;
&lt;p&gt;当初は機能を説明し、AIに関連ファイルを読ませ、実装後にテストとdiffを確認するだけだった。小さいリポジトリならこれでも十分だった。置き場所を間違えても変更範囲が狭いので、あとから直すのも難しくない。&lt;/p&gt;
&lt;p&gt;BiliKitでは、間違いがもっと見つけにくい形で出るようになった。&lt;/p&gt;
&lt;h2&gt;コンパイルが通るタイプの間違い&lt;/h2&gt;
&lt;p&gt;再生処理だけでも、ネットワーク、HTTP Range、SIDX解析、HLSプレイリストの生成、ローカルHTTPサーバー、AVPlayerのライフサイクル、キャンセル、シーク、CDNのフォールバックを通る。&lt;/p&gt;
&lt;p&gt;認証にはリモートの状態遷移、ephemeralなURLSession、リダイレクトポリシー、Keychain、リクエストへの認証情報付与、ログアウト時の削除、UI状態が関係する。弾幕もprotobufのデコード、セグメントの先読み、重複排除、共通のメディアタイムライン、レーン割り当て、Core Animation、オブジェクトの寿命までつながっている。&lt;/p&gt;
&lt;p&gt;AIがコンパイルできないSwiftを書くことは、実はそれほど多くない。困ったのは次のようなケースだった。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;型が、その意味を所有するモジュールではなく、一番置きやすいモジュールに入る&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Task.yield()&lt;/code&gt;を一度呼んだだけのテストが、たまたまその実行では通る&lt;/li&gt;
&lt;li&gt;UIの更新は止まったのに、キャンセル後も下のリソースが残る&lt;/li&gt;
&lt;li&gt;匿名で送るべきメディアリクエストに認証Headerが付く&lt;/li&gt;
&lt;li&gt;一つの判断材料を得るためのBenchmarkが、先に小さなFrameworkへ成長する&lt;/li&gt;
&lt;li&gt;Roadmap上の予定が、現在使える機能としてドキュメントに書かれる&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;どれも一見すると普通の変更なので、diffを軽く読んだだけでは見逃しやすい。テスト一式が通るものもある。&lt;/p&gt;
&lt;p&gt;最初はpromptを長くして対処していた。製品スコープ、モジュール境界、過去の判断、危険な箇所、検証方法をまとめて渡せば、確かに一時的には改善する。&lt;/p&gt;
&lt;p&gt;ただ、新しい会話を始めるたびに同じ説明が必要になる。長期的なルールと、その日に取り組んでいる作業も一つの文章に混ざってしまう。これではチャット履歴でプロジェクトを管理しているようなものだった。&lt;/p&gt;
&lt;h2&gt;プロジェクトの情報をリポジトリへ戻す&lt;/h2&gt;
&lt;p&gt;BiliKitにはRoadmap、ADR、Threat Model、日付付きの検証記録、開発ガイドを少しずつ追加した。&lt;/p&gt;
&lt;p&gt;現在のコードとビルド設定は、今何が存在するかを示す。ADRには簡単に覆したくない判断を残す。Roadmapではv1の範囲と将来の候補を分ける。検証記録は、特定の日付と環境で実際に観測した結果を保存する。&lt;/p&gt;
&lt;p&gt;BiliKitには、ダウンロード、トランスコード、ライブ配信、複数アカウント、リージョン解除など、実装案を作りやすい寄り道がかなりある。今のv1には入れない。&lt;/p&gt;
&lt;p&gt;少し大きな変更では、実装前に短い契約も書くようになった。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Goal:&lt;/strong&gt; どの観測可能な動作を変えるか&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Context:&lt;/strong&gt; 関連する入口、既存の判断、既知の制限&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Constraints:&lt;/strong&gt; 依存方向、セキュリティ、キャンセル、寿命、スコープ&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Done when:&lt;/strong&gt; 何を確認できれば完了か&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Goalを一文で書けないときは、だいたい一つのタスクに複数の変更が入っている。「テストが通る」しか完了条件がない場合は、危ない動作をまだ特定できていないことが多い。&lt;/p&gt;
&lt;p&gt;新しい会話は、前の会話を丸ごと引き継がなくてもよくなった。契約とリポジトリ内の資料を読めば、必要なところから始められる。&lt;/p&gt;
&lt;h2&gt;一つの動作を最後までつなぐ&lt;/h2&gt;
&lt;p&gt;AIに横方向のコードを一気に作らせるのは簡単だ。&lt;/p&gt;
&lt;p&gt;Domain Modelを全部作り、次にRepository、Use Case、Viewを並べる。見た目はきれいだが、実際の呼び出し元がないProtocolや、将来を想像して作ったTargetも増えやすい。&lt;/p&gt;
&lt;p&gt;BiliKitのレイヤー構成は次のようになっている。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Scene → View → ViewModel → Use Case → Repository Port → Adapter
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;実装するときは、一つの動作についてこのレイヤーを縦に通す。&lt;/p&gt;
&lt;p&gt;認証ならQRの状態、Credentialの所有権、認証が必要なEndpoint一つ、UIフロー、再起動後の復元、ログアウトまでをつなぐ。弾幕なら共通のメディアタイムラインとセグメント契約を決め、上限のあるスケジューリングを作り、実際の再生経路にRendererを接続する。&lt;/p&gt;
&lt;p&gt;サブシステム全体を一度に生成するより地味だが、あとから消す空の抽象はかなり減った。&lt;/p&gt;
&lt;h2&gt;diffの大きさでは判断できない&lt;/h2&gt;
&lt;p&gt;リダイレクト処理の5行でCredentialを外部へ送ってしまうことがある。Taskの所有関係を少し変えただけでリークや古い結果の上書きが起きる。短い永続化Migrationでもデータは消せる。&lt;/p&gt;
&lt;p&gt;一方、自動生成されたprotobufの差分は大きくても、ほぼ機械的な変更かもしれない。&lt;/p&gt;
&lt;p&gt;そこで現在は、失敗したときの影響で作業を分けている。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Green:&lt;/strong&gt; ローカルなUI変更や狭い機械的修正&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Yellow:&lt;/strong&gt; 通常のFeature、Use Case、複数ファイルのRefactor、公開API&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Red:&lt;/strong&gt; 認証、Keychain、メディア、リダイレクト、ローカルサーバー、並行処理、リソース寿命、Renderer、Migration、削除、不可逆な変更&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Greenは基本チェックと対象diffの確認で済ませる。Yellowではタスク契約を書き、必要なら別の読み取り専用コンテキストでReviewする。Redは先に進め方を決め、ルートが不明な場合は範囲を限定した実験を行い、そのリスクに合った測定を追加する。&lt;/p&gt;
&lt;p&gt;全部の変更に同じ手順を要求しないための分類でもある。CSS変更とKeychain変更に同じChecklistを使ってもあまり意味がない。&lt;/p&gt;
&lt;p&gt;不確実な実験には、先に複雑さの上限を置くようにした。弾幕Rendererの比較なら、候補を十個も用意したり、再利用可能なBenchmark Frameworkを作ったりする必要はない。製品側の判断に必要な結果が取れればよい。&lt;/p&gt;
&lt;p&gt;実際のRenderer比較は、マージしないSpikeブランチで合成負荷を使って行った。方針を決めたあと、プロダクション実装は改めて始めた。実験コードが動くという理由だけで、そのまま基盤にはしなかった。&lt;/p&gt;
&lt;h2&gt;テストが見ている範囲&lt;/h2&gt;
&lt;p&gt;字幕のテストで、一度だけ&lt;code&gt;Task.yield()&lt;/code&gt;を呼べば非同期Streamの状態が更新される、と仮定していたことがある。&lt;/p&gt;
&lt;p&gt;普段は通っていたが、統一Gateから全体を別のタイミングで実行したときにRaceが出た。何度か再実行してGreenにするのでは意味がないので、必要な状態が観測できるまで待つテストへ直した。&lt;/p&gt;
&lt;p&gt;BiliKitでは、静的チェック、Package Test、App全体のビルドを一つの入口から実行する。そのうえで、変更内容に応じた確認を追加している。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;プロトコル動作には決定的なFixture&lt;/li&gt;
&lt;li&gt;送信元とリダイレクトポリシーにはNegative Test&lt;/li&gt;
&lt;li&gt;Keychainには署名済みAppでのSmoke Test&lt;/li&gt;
&lt;li&gt;再生には実際のAVPlayer Probe&lt;/li&gt;
&lt;li&gt;弾幕には制御された高密度負荷&lt;/li&gt;
&lt;li&gt;メモリと解放には長時間の測定&lt;/li&gt;
&lt;li&gt;自動化で判断しにくいUIには手動確認&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Unsigned BuildではKeychainを確認できない。起動に成功しても、Rendererが30分後にLayerを正しく解放しているかは分からない。&lt;/p&gt;
&lt;p&gt;もちろん毎回すべてを実行するわけではない。Swift Packageだけの変更ならPackage Testで終わることもあるし、性能の問題がなければInstrumentsを開く必要もない。&lt;/p&gt;
&lt;h2&gt;今の進め方&lt;/h2&gt;
&lt;p&gt;今はだいたい次の順番でBiliKitの作業を進めている。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;現在のコード、Roadmap、関連する判断、テストを読む&lt;/li&gt;
&lt;li&gt;観測可能なGoalと完了条件を書く&lt;/li&gt;
&lt;li&gt;失敗時の影響からリスクを決める&lt;/li&gt;
&lt;li&gt;不確実な方法を試す前に複雑さの上限を置く&lt;/li&gt;
&lt;li&gt;最小の縦方向Sliceを実装する&lt;/li&gt;
&lt;li&gt;重要な変更は新しい読み取り専用コンテキストでReviewする&lt;/li&gt;
&lt;li&gt;共通の自動Gateを実行する&lt;/li&gt;
&lt;li&gt;その変更に必要な環境依存の確認だけ追加する&lt;/li&gt;
&lt;li&gt;現在のドキュメントを更新し、過去の検証記録は書き換えない&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;週末に作る小さなScriptなら、ここまでする必要はない。BiliKitには認証、再生、ローカルサーバー、並行状態、Rendererがあるので、prompt、テスト、diff確認だけの進め方では早い段階で足りなくなった。&lt;/p&gt;
&lt;p&gt;BiliKitはまだv1前で、このやり方も今後また変わると思う。&lt;/p&gt;
&lt;p&gt;少なくとも、新しい会話にプロジェクト全体を覚えてもらう必要はなくなった。&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;&lt;em&gt;この経験の一部は、あとから再利用できるCodex Skillsとして切り出した。続きは&lt;a href=&quot;/ja/posts/i-turned-my-ai-workflow-into-code/&quot;&gt;自分のAIワークフローをコードにした&lt;/a&gt;。&lt;/em&gt;&lt;/p&gt;
</content:encoded></item><item><title>Swift の新機能：WWDC26 メモ</title><link>https://www.shiinayane.com/ja/posts/whats-new-in-swift-wwdc26/</link><guid isPermaLink="true">https://www.shiinayane.com/ja/posts/whats-new-in-swift-wwdc26/</guid><description>WWDC26 の「What&apos;s New in Swift」を見ながらまとめた、Swift 6.3 と 6.4 の変更点。</description><pubDate>Thu, 11 Jun 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;今年の &lt;em&gt;What&apos;s New in Swift&lt;/em&gt; を見たので、あとで確認しそうな内容をまとめておく。&lt;/p&gt;
&lt;p&gt;Becca と Evan による Swift 6.3、6.4 のセッションで、細かい構文の変更から Android、Wasm、所有権まですごい量だった。リファレンスとして使いたいので、コード例もそのまま残している。&lt;/p&gt;
&lt;h2&gt;普段のコードに関係する変更&lt;/h2&gt;
&lt;h3&gt;&lt;code&gt;some&lt;/code&gt; と &lt;code&gt;any&lt;/code&gt; の Optional&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;some&lt;/code&gt; や &lt;code&gt;any&lt;/code&gt; を Optional にするとき、外側の括弧が不要になった。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// Before
func delegate() -&amp;gt; (any Renderer)?

// After
func delegate() -&amp;gt; any Renderer?
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;小さい変更だけど、こちらのほうが読みやすい。&lt;/p&gt;
&lt;h3&gt;&lt;code&gt;weak let&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;これまで弱参照は &lt;code&gt;var&lt;/code&gt; にする必要があったため、&lt;code&gt;Sendable&lt;/code&gt; な型では扱いづらく、&lt;code&gt;@unchecked Sendable&lt;/code&gt; で回避するケースもあった。&lt;/p&gt;
&lt;p&gt;Swift 6.4 では不変の弱参照を &lt;code&gt;weak let&lt;/code&gt; として宣言できる。&lt;/p&gt;
&lt;h3&gt;&lt;code&gt;~Sendable&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;~Sendable&lt;/code&gt; を使うと、その型自体が &lt;code&gt;Sendable&lt;/code&gt; ではないことを明示しつつ、サブクラスが &lt;code&gt;Sendable&lt;/code&gt; になる可能性は残せる。&lt;/p&gt;
&lt;h3&gt;Task のエラーを無視した場合の警告&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;Task&lt;/code&gt; から投げられたエラーを無視すると警告が出るようになった。非構造化 Task のエラーは気づかないまま消えやすいので、これは普通に助かる。&lt;/p&gt;
&lt;h3&gt;&lt;code&gt;defer&lt;/code&gt; から async 関数を呼べる&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;defer&lt;/code&gt; ブロック内で &lt;code&gt;async&lt;/code&gt; 関数を呼べるようになった。&lt;/p&gt;
&lt;h3&gt;2 つの memberwise initializer&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;internal&lt;/code&gt; と &lt;code&gt;private&lt;/code&gt; の stored property が混在する struct では、それぞれのアクセスレベルに対応する 2 つの memberwise initializer を合成できる。可視性が違うという理由だけで initializer を手書きする必要がなくなる。&lt;/p&gt;
&lt;h3&gt;&lt;code&gt;@diagnose&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;@diagnose&lt;/code&gt; は、特定の宣言に対して 1 つの診断レベルを変更する属性。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;@diagnose(DeprecatedDeclaration, as: ignored, reason: &quot;Flying with surplus hardware&quot;)
func makeApolloSoyuzMission() -&amp;gt; Mission { ... }

@diagnose(StrictMemorySafety, as: warning)
func uplinkCommand(from receiver: inout Receiver, to computer: inout Computer) { ... }

@diagnose(ErrorInFutureSwiftVersion, as: error)
func fetchPosition() -&amp;gt; (x: Double, y: Double, z: Double) { ... }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;今回の細かい変更では、個人的にこれが一番好き。特殊な call site のためにプロジェクト全体の警告設定を変えなくて済むし、&lt;code&gt;reason:&lt;/code&gt; に例外の理由も残せる。&lt;/p&gt;
&lt;h3&gt;モジュールセレクタ &lt;code&gt;::&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;Swift 6.3 では、複数モジュールの同名シンボルを区別するための &lt;code&gt;::&lt;/code&gt; が追加された。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import Rocket
import GiftShopToys

let r1 = SaturnV()          // ambiguous
let r2 = Rocket::SaturnV()  // Rocket モジュールの型
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;メンバーにも使える。たとえば &lt;code&gt;technician.HumanResources::fire()&lt;/code&gt; のようにモジュールを指定できる。&lt;/p&gt;
&lt;h2&gt;ライブラリ&lt;/h2&gt;
&lt;h3&gt;標準ライブラリ&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;withTaskCancellationShield { ... }&lt;/code&gt;：外側の Task がキャンセルされても、クリティカルな処理を最後まで実行する。セッションでは最後の SOS パケット送信が例として使われていた。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;Dictionary.mapKeyedValues&lt;/code&gt;：value の変換時に key も参照できる。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;missions.mapKeyedValues { mission, window in
    makeDisplayName(for: mission, in: window)
}
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;FilePath&lt;/code&gt;：構造化された &lt;code&gt;components&lt;/code&gt; を持つクロスプラットフォームのパス型。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;var path: FilePath = &quot;/var/www/static&quot;
path.components.append(&quot;WWDC&quot;)
// [ &quot;var&quot;, &quot;www&quot;, &quot;static&quot;, &quot;WWDC&quot; ]
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Swift Testing&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;Issue.record(..., severity: .warning)&lt;/code&gt; はテストを失敗させずに警告を記録する。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;try Test.cancel(&quot;reason&quot;)&lt;/code&gt; は、該当しないパラメータ化テストを理由つきで終了できる。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;swift test&lt;/code&gt; で、設定した最大回数までテストを pass または fail するまで繰り返せる。不安定なテストを調べるときに使えそう。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;XCTestCase&lt;/code&gt; 内で &lt;code&gt;#expect&lt;/code&gt; が使えるようになり、XCTest の assertion failure も Swift Testing の Issue として表示される。既存テストを一気に移行しなくてもよい。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Subprocess 1.0&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;Subprocess&lt;/code&gt; パッケージが 1.0 になった。API とエラー処理が整理され、クロスプラットフォームで出力を 1 行ずつストリーミングできる。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;let result = try await Subprocess.run(
    .name(&quot;ls&quot;),
    input: .none,
    output: .sequence,
    error: .string(limit: 4096)
) { execution in
    execution.standardOutput.strings().filter { $0.hasSuffix(&quot;.obj&quot;) }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;プラットフォームごとの file descriptor や終了ステータスの違いはパッケージ側で吸収される。&lt;/p&gt;
&lt;h3&gt;Foundation の &lt;code&gt;ProgressManager&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;ProgressManager&lt;/code&gt; は &lt;code&gt;async&lt;/code&gt;／&lt;code&gt;await&lt;/code&gt; 向けの新しい進捗 API。親は &lt;code&gt;subprogress(assigningCount:)&lt;/code&gt; で全体の一部を子に割り当て、子は親の合計値を知らなくても自分のステージだけを報告できる。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;let manager = ProgressManager(totalCount: 100)
try await rocket.launch(manager.subprogress(assigningCount: 100))

extension Rocket {
    func launch(_ progress: consuming Subprogress? = nil) async throws {
        let stage = progress?.start(totalCount: 3)
        try await ignite();          stage?.complete(count: 1)
        try await liftoff();         stage?.complete(count: 1)
        try await stageSeparation(); stage?.complete(count: 1)
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Foundation の Pure Swift 化も続いている。&lt;code&gt;Data&lt;/code&gt; の処理と bridging の高速化、&lt;code&gt;NSURL&lt;/code&gt;／&lt;code&gt;CFURL&lt;/code&gt; の背後にある Swift 実装の統一が紹介された。&lt;/p&gt;
&lt;h2&gt;Apple プラットフォーム以外&lt;/h2&gt;
&lt;h3&gt;&lt;code&gt;anyAppleOS&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;Apple OS をすべて並べていた availability を短く書ける。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// Before
@available(macOS 27, iOS 27, watchOS 27, tvOS 27, visionOS 27, *)
func showStatus() { ... }

// After
@available(anyAppleOS 27, *)
func showStatus() { ... }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;#if os(anyAppleOS)&lt;/code&gt; でも使用できる。&lt;/p&gt;
&lt;h3&gt;&lt;code&gt;@C&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;@C&lt;/code&gt; は Swift 関数を C に公開し、Swift で C 関数を実装する場合にも使える。整数、ポインタ、import した C struct、raw-value enum など、C 互換の型を使う必要がある。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;@C
func averageLaunchWindowLength(_ windows: Span&amp;lt;LaunchWindow&amp;gt;) -&amp;gt; TimeInterval { ... }
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Java と Android&lt;/h3&gt;
&lt;p&gt;Java から Swift の &lt;code&gt;async&lt;/code&gt; 関数と throwing 関数を呼べるようになり、Java クラスから Swift protocol に準拠することもできる。swift.org では公式の Swift SDK for Android も公開された。&lt;/p&gt;
&lt;h3&gt;WebAssembly&lt;/h3&gt;
&lt;p&gt;Swift はオープンソースのツールチェーンで Wasm にコンパイルできる。JavaScriptKit の型安全な bridge は、dynamic な経路より &lt;strong&gt;35〜40 倍&lt;/strong&gt;高速になった。セッションでは、Goodnotes がネイティブ iOS アプリの Swift コードを Wasm 経由で Web に持っていった事例が紹介された。&lt;/p&gt;
&lt;h3&gt;Embedded Swift&lt;/h3&gt;
&lt;p&gt;Embedded Swift では existential type、untyped &lt;code&gt;throws&lt;/code&gt;、制約のあるハードウェアで coredump を調査するための DWARF debug info が追加された。新しい &lt;code&gt;EmbeddedRestrictions&lt;/code&gt; 警告グループは、組み込み環境で利用できない機能を知らせる。&lt;/p&gt;
&lt;h3&gt;エディタ&lt;/h3&gt;
&lt;p&gt;Swift VS Code extension はツールチェーン管理用の Swiftly を統合し、OpenVSX でも公開された。Cursor や VSCodium からも利用でき、初心者向けの getting-started checklist も追加されている。&lt;/p&gt;
&lt;h2&gt;パフォーマンスと所有権&lt;/h2&gt;
&lt;h3&gt;オプティマイザへのヒント&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;@inline(always)&lt;/code&gt; が正式にサポートされた。クラスメソッドでは &lt;code&gt;final&lt;/code&gt; と組み合わせる。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Swift 6.3 の &lt;code&gt;@specialized&lt;/code&gt; は、よく使われる具体的な型に対して generic function を事前に特殊化できる。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;@specialized(where Values == [UInt8])
func histogram&amp;lt;Values&amp;gt;(of values: Values) -&amp;gt; [256 of Int]
    where Values: Sequence&amp;lt;UInt8&amp;gt; { ... }
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;標準ライブラリの所有権対応&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;Equatable&lt;/code&gt;、&lt;code&gt;Comparable&lt;/code&gt;、&lt;code&gt;Hashable&lt;/code&gt; が noncopyable type に対応した。&lt;code&gt;Equatable&lt;/code&gt; と &lt;code&gt;Comparable&lt;/code&gt; は non-escapable type にも対応し、associated type には &lt;code&gt;~Copyable&lt;/code&gt; または &lt;code&gt;~Escapable&lt;/code&gt; を指定できる。&lt;/p&gt;
&lt;p&gt;新しい &lt;code&gt;Iterable&lt;/code&gt; protocol の &lt;code&gt;for&lt;/code&gt; loop は、要素をコピーせず borrow する。&lt;/p&gt;
&lt;p&gt;カスタムの &lt;code&gt;borrow&lt;/code&gt;／&lt;code&gt;mutate&lt;/code&gt; accessor を使うと、container からもこのセマンティクスを公開できる。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;public struct UniqueBox&amp;lt;Value: ~Copyable&amp;gt;: ~Copyable {
    private let valuePointer: UnsafeMutablePointer&amp;lt;Value&amp;gt;

    public var value: Value {
        borrow { valuePointer.pointee }
        mutate { &amp;amp;valuePointer.pointee }
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;新しい低オーバーヘッド型&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;UniqueBox&lt;/code&gt;／&lt;code&gt;UniqueArray&lt;/code&gt;：参照カウントのコストがない noncopyable storage。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;Continuation&lt;/code&gt;：1 回だけ resume されることをコンパイル時に検証する。&lt;code&gt;CheckedContinuation&lt;/code&gt; と同じ安全性で、コストは &lt;code&gt;UnsafeContinuation&lt;/code&gt; 相当。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;Ref&lt;/code&gt;／&lt;code&gt;MutableRef&lt;/code&gt;：collection 内の要素を安全に borrow する。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;var countRef = MutableRef(&amp;amp;counts[key, default: 0])
countRef.value += 1
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;withTemporaryAllocation&lt;/code&gt; は &lt;code&gt;UnsafeMutableBufferPointer&lt;/code&gt; の代わりに &lt;code&gt;OutputSpan&lt;/code&gt; を渡す。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;自分が先に使いそうなのは &lt;code&gt;@diagnose&lt;/code&gt;、Task のエラー警告、Swift Testing の変更あたり。所有権関係の型は面白いけど、実際のプロジェクトでちょうどいい問題が出てきてから試したい。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;セッション全編：&lt;a href=&quot;https://developer.apple.com/videos/play/wwdc2026/262/&quot;&gt;What&apos;s new in Swift — WWDC26&lt;/a&gt;。&lt;/p&gt;
&lt;/blockquote&gt;
</content:encoded></item><item><title>設定は簡単だった。大変なのは維持することだ</title><link>https://www.shiinayane.com/ja/posts/maintenance/</link><guid isPermaLink="true">https://www.shiinayane.com/ja/posts/maintenance/</guid><description>整えた開発環境も、使っているうちに少しずつずれていく。掃除を大仕事にしないために、普段確認していることをまとめた。</description><pubDate>Sat, 30 May 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;一度きれいにした開発環境も、だいたい半年ほど経つと崩れてくる。この前きちんと調べたときは、1つだと思っていたPythonが3バージョンあり、Brewfileと実際のマシンが一致せず、&lt;code&gt;mise list&lt;/code&gt;には何の実験で入れたのか思い出せないruntimeが並んでいた。何かが壊れたわけではない。毎日使う間に、元の状態から静かにずれていただけだった。&lt;/p&gt;
&lt;p&gt;この記事でシリーズは最後になる。これまでの6本で扱ったのはレイヤー、設定、dotfilesだったが、そこまでは午後のうちに終えられる。1年後も同じ環境を保てるかどうかは、その後もずれを見つけ、不要になったものを取り除けるかにかかっている。&lt;/p&gt;
&lt;h2&gt;ずれ方はだいたい3種類&lt;/h2&gt;
&lt;p&gt;実際に見つかるのは、主に次の3つだった。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;ツールによる無言の自動インストール。&lt;/strong&gt; &lt;a href=&quot;/ja/posts/python/&quot;&gt;Pythonの記事&lt;/a&gt;では、&lt;code&gt;uv&lt;/code&gt;が独自のPythonをダウンロードしていた。確認を求められないまま、自分で選んでいない状態がマシンに増えるため、最も見落としやすいずれ方だ。&lt;code&gt;python-preference = only-system&lt;/code&gt;のような宣言的な設定を使えば、無言のダウンロードを明示的なエラーに変えられる。ただし、あらゆるツールを設定だけで防げるわけではないので、結果を調べる手段は別に必要になる。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;インストール後の同期忘れ。&lt;/strong&gt; 自分でアプリを入れても、宣言ファイルへの追記を忘れることがある。すると&lt;a href=&quot;/ja/posts/apps/&quot;&gt;アプリ管理の記事&lt;/a&gt;で作ったBrewfileが、実際のマシンから少しずつ遅れていく。普段は困らなくても、移行時には「これも入れていたのか」が一度に出てくる。復旧に使う段階まで放置せず、先に両方の状態を突き合わせておく必要がある。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;使い終わったものの蓄積。&lt;/strong&gt; runtimeやグローバルツール、パッケージを必要があって入れ、一度だけ使い、そのまま残す。これは特に片づけにくい。個々の項目には問題がないため、エラーは出ず、リスト全体を見たときに初めて、積み重なったこと自体が問題だと分かる。&lt;/p&gt;
&lt;h2&gt;ヘルスチェックは、ずれを見せるだけでいい&lt;/h2&gt;
&lt;p&gt;確認用に、いくつかのshell関数を残している。前の記事で紹介した&lt;code&gt;brewdiff&lt;/code&gt;はインストール済みアプリとBrewfileを比較し、&lt;code&gt;zhealth&lt;/code&gt;はホームディレクトリに散らばったzshファイルを探す。Pythonは複数のツールが別々のインタプリタを持っても気づきにくいので、専用の確認も用意した。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# 30-functions.zsh — surface Python version drift across tools
pyversions() {
  echo &quot;shell PATH   : $(command -v python)&quot;
  echo &quot;  reports    : $(python --version 2&amp;gt;&amp;amp;1)&quot;
  echo &quot;mise current : $(mise current python 2&amp;gt;/dev/null || echo &apos;—&apos;)&quot;
  echo &quot;uv would use : $(uv run python --version 2&amp;gt;&amp;amp;1)&quot;
  echo &quot;mise list    :&quot;
  mise list python 2&amp;gt;/dev/null | sed &apos;s/^/  /&apos;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;pyversions&lt;/code&gt;を実行すると、shellの&lt;code&gt;python&lt;/code&gt;、&lt;code&gt;mise current&lt;/code&gt;、&lt;code&gt;uv run python&lt;/code&gt;、そして&lt;code&gt;mise&lt;/code&gt;にインストール済みのバージョンを一度に確認できる。最初の3つが一致していれば、このレイヤーは正常だ。一致しなければ、想定外のPythonをどこかのツールが持っている。Pythonの記事で行った調査を、確認コマンドを一から思い出すことなく繰り返せる。&lt;/p&gt;
&lt;p&gt;ほかのレイヤーでも考え方は同じだ。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;mise list&lt;/code&gt;と、実際に使っている&lt;code&gt;mise.toml&lt;/code&gt;を比較する&lt;/li&gt;
&lt;li&gt;&lt;code&gt;brew bundle check&lt;/code&gt;で、マシンとBrewfileを比較する&lt;/li&gt;
&lt;li&gt;&lt;code&gt;$HOME&lt;/code&gt;の中身が「zshファイルは1つだけ」という想定に合っているか確認する&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;どのチェックも自動修復はしない。これは意図的にそうしている。報告だけを行う小さなコマンドなら、挙動を把握しやすく、実行もしやすい。偶然見つけて驚くしかなかったずれが、自分で対処を選べる差分になる。&lt;/p&gt;
&lt;h2&gt;30日使わなかったものを見直す&lt;/h2&gt;
&lt;p&gt;自分にとって成熟した環境とは、使っていないツールがほとんど残っていない環境だ。そのため、メンテナンスで行うことの多くは追加ではなく削除になる。&lt;/p&gt;
&lt;p&gt;残すか迷うものについては、過去30日に実際に使ったかを考える。1か月触っていないruntime、グローバルツール、アプリは削除候補にする。自動的に消すわけではないが、残すのが当然のものから、残す理由が必要なものへ扱いを変える。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;mise&lt;/code&gt;のruntimeなら、確認と整理は具体的だ。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ mise list                 # what&apos;s installed
$ mise uninstall python@3.11 # remove a version no project uses
$ mise prune                 # drop versions nothing references
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;前回の&lt;a href=&quot;/ja/posts/polyglot/&quot;&gt;Rubyについての判断&lt;/a&gt;は、同じ基準をインストール前に適用した例でもある。システムRubyが古いというだけで、Rubyプロジェクトのないマシンに&lt;code&gt;mise&lt;/code&gt;管理のRubyを追加する理由にはならない。必要なプロジェクトができるまで待てば、あとで確認して削除するruntimeも増えない。&lt;/p&gt;
&lt;p&gt;この作業は、整理したい気分になるのを待たず、定期的に行う。新しいツールの導入は進歩のように感じやすい一方、削除には損失や、最初の選択が間違いだったと認めるような感覚がある。サンクコストも、放置するほうへ気持ちを傾ける。決まった作業にしてしまえば、チェックを実行し、30日使っていないものを見て、残す理由を説明できないものを消すだけで済む。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;mise list&lt;/code&gt;の行が減ること、短いBrewfile、ホームディレクトリにdotfileが1つしかないことは、設定不足とは限らない。それが整理後の正しい状態である場合もある。&lt;/p&gt;
&lt;h2&gt;メンテナンスのためのシステムを増やさない&lt;/h2&gt;
&lt;p&gt;掃除の仕組み自体も、簡単に作り込みすぎてしまう。ダッシュボード、定期ジョブ、大量のヘルスチェックを揃えた結果、それらを維持する仕事が新しく増えることもある。&lt;/p&gt;
&lt;p&gt;2秒で終わる&lt;code&gt;brewdiff&lt;/code&gt;なら実際に使う。世話が必要な監視システムは使わなくなる。状態の突き合わせは、それによって避けられる混乱より低コストな間だけ役に立つ。だから確認ツールは小さく保ち、報告だけを任せ、削除の判断までは自動化しない。&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;/ja/posts/manifesto/&quot;&gt;シリーズ最初の記事&lt;/a&gt;では、環境をSystem、Runtime version、Package manager、Project dependenciesの4レイヤーに分けた。それらの設定だけでは用意できない5つ目の要素がある。たまにマシンを見直し、実際の状態と宣言した状態を比較して、壊れる前に両者を揃える習慣だ。&lt;/p&gt;
&lt;p&gt;しばらく確認していないマシンなら、まず&lt;code&gt;which python&lt;/code&gt;と&lt;code&gt;uv run python --version&lt;/code&gt;を実行し、次に&lt;code&gt;mise list&lt;/code&gt;から1か月触っていないものを探す。それだけでも、自分が覚えている環境と、手元にある環境がまだ同じかどうかは見えてくる。&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;&lt;em&gt;この記事はSovereign Toolsシリーズの最終回です。全記事の順番は&lt;a href=&quot;/ja/series/sovereign-tools&quot;&gt;シリーズ一覧&lt;/a&gt;にまとめています。&lt;/em&gt;&lt;/p&gt;
</content:encoded></item><item><title>一つの設計を、複数の言語で使う</title><link>https://www.shiinayane.com/ja/posts/polyglot/</link><guid isPermaLink="true">https://www.shiinayane.com/ja/posts/polyglot/</guid><description>同じ4層構造をNode、Java、Swift、Rust、Go、Rubyへ適用するための実践リファレンス。</description><pubDate>Sat, 30 May 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;慣れていない言語でプロジェクトを始めるとき、最初に知りたいことはだいたい決まっている。ランタイムは何で入れるのか、パッケージは何で管理するのか、どのロックファイルをgitに入れ、どの生成物を除外するのか。この記事では、Node、Java、Swift、Rust、Go、Rubyについて、その答えを一か所にまとめた。必要な章だけ読み返すためのリファレンスとして使う。&lt;/p&gt;
&lt;p&gt;前提となるのは、&lt;a href=&quot;/ja/posts/manifesto/&quot;&gt;シリーズ最初の記事&lt;/a&gt;で導入した4層構造だ。&lt;/p&gt;
&lt;p&gt;&amp;lt;figure class=&quot;my-6&quot;&amp;gt;
&amp;lt;svg viewBox=&quot;0 0 600 330&quot; role=&quot;img&quot; aria-labelledby=&quot;diagram-layers-title-6&quot; style=&quot;width:100%;height:auto;color:inherit&quot;&amp;gt;
&amp;lt;title id=&quot;diagram-layers-title-6&quot;&amp;gt;The four-layer stack: System, Runtime version, Package manager, Project dependencies&amp;lt;/title&amp;gt;
&amp;lt;g font-family=&quot;ui-sans-serif, system-ui, sans-serif&quot;&amp;gt;
&amp;lt;rect x=&quot;10&quot; y=&quot;10&quot; width=&quot;580&quot; height=&quot;66&quot; rx=&quot;10&quot; fill=&quot;currentColor&quot; fill-opacity=&quot;0.05&quot; stroke=&quot;currentColor&quot; stroke-opacity=&quot;0.15&quot;/&amp;gt;
&amp;lt;text x=&quot;30&quot; y=&quot;38&quot; font-size=&quot;13&quot; font-weight=&quot;700&quot; fill=&quot;var(--primary)&quot;&amp;gt;Layer 3&amp;lt;/text&amp;gt;
&amp;lt;text x=&quot;30&quot; y=&quot;58&quot; font-size=&quot;15&quot; font-weight=&quot;600&quot; fill=&quot;currentColor&quot;&amp;gt;Project dependencies&amp;lt;/text&amp;gt;
&amp;lt;text x=&quot;570&quot; y=&quot;44&quot; font-size=&quot;13&quot; text-anchor=&quot;end&quot; fill=&quot;currentColor&quot; fill-opacity=&quot;0.6&quot;&amp;gt;lockfiles in git&amp;lt;/text&amp;gt;
&amp;lt;rect x=&quot;10&quot; y=&quot;86&quot; width=&quot;580&quot; height=&quot;66&quot; rx=&quot;10&quot; fill=&quot;currentColor&quot; fill-opacity=&quot;0.05&quot; stroke=&quot;currentColor&quot; stroke-opacity=&quot;0.15&quot;/&amp;gt;
&amp;lt;text x=&quot;30&quot; y=&quot;114&quot; font-size=&quot;13&quot; font-weight=&quot;700&quot; fill=&quot;var(--primary)&quot;&amp;gt;Layer 2&amp;lt;/text&amp;gt;
&amp;lt;text x=&quot;30&quot; y=&quot;134&quot; font-size=&quot;15&quot; font-weight=&quot;600&quot; fill=&quot;currentColor&quot;&amp;gt;Package manager&amp;lt;/text&amp;gt;
&amp;lt;text x=&quot;570&quot; y=&quot;120&quot; font-size=&quot;13&quot; text-anchor=&quot;end&quot; fill=&quot;currentColor&quot; fill-opacity=&quot;0.6&quot;&amp;gt;uv, pnpm, cargo&amp;lt;/text&amp;gt;
&amp;lt;rect x=&quot;10&quot; y=&quot;162&quot; width=&quot;580&quot; height=&quot;66&quot; rx=&quot;10&quot; fill=&quot;currentColor&quot; fill-opacity=&quot;0.05&quot; stroke=&quot;currentColor&quot; stroke-opacity=&quot;0.15&quot;/&amp;gt;
&amp;lt;text x=&quot;30&quot; y=&quot;190&quot; font-size=&quot;13&quot; font-weight=&quot;700&quot; fill=&quot;var(--primary)&quot;&amp;gt;Layer 1&amp;lt;/text&amp;gt;
&amp;lt;text x=&quot;30&quot; y=&quot;210&quot; font-size=&quot;15&quot; font-weight=&quot;600&quot; fill=&quot;currentColor&quot;&amp;gt;Runtime version&amp;lt;/text&amp;gt;
&amp;lt;text x=&quot;570&quot; y=&quot;196&quot; font-size=&quot;13&quot; text-anchor=&quot;end&quot; fill=&quot;currentColor&quot; fill-opacity=&quot;0.6&quot;&amp;gt;mise, or a sovereign tool&amp;lt;/text&amp;gt;
&amp;lt;rect x=&quot;10&quot; y=&quot;238&quot; width=&quot;580&quot; height=&quot;66&quot; rx=&quot;10&quot; fill=&quot;currentColor&quot; fill-opacity=&quot;0.08&quot; stroke=&quot;currentColor&quot; stroke-opacity=&quot;0.2&quot;/&amp;gt;
&amp;lt;text x=&quot;30&quot; y=&quot;266&quot; font-size=&quot;13&quot; font-weight=&quot;700&quot; fill=&quot;var(--primary)&quot;&amp;gt;Layer 0&amp;lt;/text&amp;gt;
&amp;lt;text x=&quot;30&quot; y=&quot;286&quot; font-size=&quot;15&quot; font-weight=&quot;600&quot; fill=&quot;currentColor&quot;&amp;gt;System&amp;lt;/text&amp;gt;
&amp;lt;text x=&quot;570&quot; y=&quot;272&quot; font-size=&quot;13&quot; text-anchor=&quot;end&quot; fill=&quot;currentColor&quot; fill-opacity=&quot;0.6&quot;&amp;gt;Homebrew + Xcode CLT&amp;lt;/text&amp;gt;
&amp;lt;/g&amp;gt;
&amp;lt;/svg&amp;gt;
&amp;lt;/figure&amp;gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Layer 1 — Runtime version&lt;/strong&gt; の担当は、&lt;a href=&quot;/ja/posts/sovereignty/&quot;&gt;ツールの主権についての記事&lt;/a&gt;で決めた基準に従う。言語に強力な公式ツールがあればそれに任せ、なければ &lt;code&gt;mise&lt;/code&gt; を使う。以下では結論の理由を繰り返さず、実際の設定と、間違えやすい点に絞る。&lt;/p&gt;
&lt;h2&gt;Node.js / TypeScript&lt;/h2&gt;
&lt;p&gt;Nodeには主権を持つバージョン管理ツールがないため、Nodeのバージョンは &lt;code&gt;mise&lt;/code&gt; に任せる。パッケージマネージャーには &lt;code&gt;pnpm&lt;/code&gt; を使うが、別途 &lt;code&gt;brew install&lt;/code&gt; はせず、&lt;code&gt;corepack&lt;/code&gt; から有効にする。こうすると、&lt;code&gt;package.json&lt;/code&gt; の &lt;code&gt;packageManager&lt;/code&gt; フィールドでプロジェクトごとに &lt;code&gt;pnpm&lt;/code&gt; のバージョンを固定できる。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# mise.toml
[tools]
node = &quot;22&quot;

[settings]
# let corepack manage the pnpm version from package.json&apos;s packageManager field
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;corepack enable      # ships with Node; activates pnpm/yarn shims
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;brew install node&lt;/code&gt; も &lt;code&gt;brew install pnpm&lt;/code&gt; も使わない。前者はLayer 1の所有権をHomebrewへ渡してしまい、後者はプロジェクト側のパッケージマネージャー固定を迂回してしまう。Nodeには別の仮想環境もいらない。&lt;code&gt;node_modules&lt;/code&gt; がもともとプロジェクト単位なので、Pythonが &lt;code&gt;.venv&lt;/code&gt; で得ている隔離は最初から存在する。&lt;/p&gt;
&lt;p&gt;Node製のCLIを一度だけ実行するなら、通常は &lt;code&gt;pnpm dlx&lt;/code&gt; を使う。継続的にインストールする必要がある場合は、&lt;code&gt;PATH&lt;/code&gt; に追加した &lt;code&gt;PNPM_HOME&lt;/code&gt; を明示的に管理し、&lt;code&gt;npm install -g&lt;/code&gt; を無秩序に増やさない。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;pnpm-lock.yaml&lt;/code&gt; はコミットし、次を除外する。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;node_modules/
*.tsbuildinfo
.turbo/
dist/
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Java&lt;/h2&gt;
&lt;p&gt;Javaには統一された主権バージョンツールがなく、JDKにも複数のディストリビューションがある。私は、中立的でよく保守されているTemurinを &lt;code&gt;mise&lt;/code&gt; から入れている。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# mise.toml
[tools]
java = &quot;temurin-21&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;java&lt;/code&gt; が &lt;code&gt;PATH&lt;/code&gt; に入っていることだけでなく、&lt;code&gt;mise&lt;/code&gt; が &lt;code&gt;JAVA_HOME&lt;/code&gt; を正しく設定しているかも確認する。Javaのツールには &lt;code&gt;JAVA_HOME&lt;/code&gt; を直接読むものが多く、古いインストールの値が残っていると、意図したJDKよりそちらが静かに優先される。&lt;code&gt;mise where java&lt;/code&gt; と &lt;code&gt;echo $JAVA_HOME&lt;/code&gt; は同じ場所を指すべきだ。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;mise where java
echo $JAVA_HOME
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;ビルドには、グローバルなGradleやMavenではなく、リポジトリに含まれる &lt;code&gt;./gradlew&lt;/code&gt; または &lt;code&gt;./mvnw&lt;/code&gt; を使う。wrapperはビルドツールのバージョンをリポジトリ内で固定する。通常の依存関係だけでなく、ビルドツールにもLayer 3と同じ考え方を適用できる。&lt;/p&gt;
&lt;p&gt;Androidは別扱いでよい。Android Studioは自身のJDKとSDKを同梱しているため、SwiftをXcodeに任せるのと同様に、その環境はAndroid Studioに任せる。&lt;/p&gt;
&lt;p&gt;GradleとMavenはビルドファイルで依存関係を宣言する。ビルドファイルとwrapperはコミットし、出力は除外する。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;.gradle/
build/
target/
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Swift / iOS&lt;/h2&gt;
&lt;p&gt;macOSにおけるSwiftの主権ツールはXcodeだ。Xcodeは &lt;strong&gt;Layer 0 — System&lt;/strong&gt; と &lt;strong&gt;Layer 1 — Runtime version&lt;/strong&gt; の両方をまたぎ、ツールチェーン、SDK、ビルドシステムを一つのアプリとして提供する。私はMac App StoreからXcodeを入れているため、&lt;a href=&quot;/ja/posts/apps/&quot;&gt;アプリ管理の記事&lt;/a&gt;では &lt;code&gt;mas&lt;/code&gt; の担当にしている。依存関係にはSwift Package Managerを優先する。&lt;/p&gt;
&lt;p&gt;macOSで &lt;code&gt;brew install swift&lt;/code&gt; はしない。ツールチェーンの所有者が二つに増える一方で、ビルドの別の部分は引き続きXcodeが握るため、分かりにくい競合が起きる。CocoaPodsも、多くの新しいプロジェクトでは不要になったRuby依存を追加する。必要なパッケージがSPMに対応していれば、依存関係の管理をAppleのツールチェーン内で完結できる。&lt;/p&gt;
&lt;p&gt;自分のプロジェクトには、うまく機能している境界の例がある。KotobaLabはSwift、付属するDictionaryBuilderはPythonで書かれている。二つはランタイムもパッケージマネージャーもビルドシステムも共有せず、一つのSQLiteファイルだけで接続する。DictionaryBuilderがデータベースを書き、KotobaLabが読む。中立なデータインターフェースを挟むことで、どちらのツールチェーンも相手の内部を知る必要がない。&lt;/p&gt;
&lt;p&gt;SPMのロックファイルである &lt;code&gt;Package.resolved&lt;/code&gt; はコミットする。ユーザー固有のファイルとビルド生成物は除外する。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;xcuserdata/
DerivedData/
.build/
*.xcuserstate
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Rust&lt;/h2&gt;
&lt;p&gt;この中では、Rustの所有関係が最も単純だ。&lt;code&gt;rustup&lt;/code&gt; がLayer 1を持ち、同梱される &lt;code&gt;cargo&lt;/code&gt; がLayer 2に加えて、ビルド、テスト、依存関係の管理、公開まで担当する。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# rustup installs the toolchain; cargo comes with it
rustup default stable
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;多言語リポジトリにすでに &lt;code&gt;mise.toml&lt;/code&gt; がある場合は、そこへ &lt;code&gt;rust = &quot;1.78&quot;&lt;/code&gt; と書き、&lt;code&gt;mise&lt;/code&gt; から &lt;code&gt;rustup&lt;/code&gt; へ委譲してもよい。&lt;a href=&quot;/ja/posts/sovereignty/&quot;&gt;ツールの主権についての記事&lt;/a&gt;で説明した &lt;strong&gt;mise as proxy&lt;/strong&gt; の形だ。どちらでも動くが、同じプロジェクトのRustを両方から管理してはいけない。&lt;/p&gt;
&lt;p&gt;Cargoは依存関係をプロジェクト単位で扱うため、Rustに仮想環境は必要ない。&lt;code&gt;ripgrep&lt;/code&gt;、&lt;code&gt;fd&lt;/code&gt;、&lt;code&gt;bat&lt;/code&gt; のようにRustで書かれたグローバルCLIには、&lt;code&gt;brew&lt;/code&gt; のformulaを選ぶ。Homebrewならビルド済みのバイナリが入るが、&lt;code&gt;cargo install&lt;/code&gt; はソースからコンパイルする。まだパッケージ化されていないツールにだけ &lt;code&gt;cargo install&lt;/code&gt; を使えばよい。&lt;/p&gt;
&lt;p&gt;アプリケーションやバイナリでは &lt;code&gt;Cargo.lock&lt;/code&gt; をコミットする。ライブラリでは、利用側が自分でバージョンを解決できるよう、コミットしないのが長年の慣例だ。次を除外する。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;/target/
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Go&lt;/h2&gt;
&lt;p&gt;Go公式の &lt;code&gt;dl&lt;/code&gt; インストーラーは強力なバージョン管理ツールとはいえないので、Goのバージョンは &lt;code&gt;mise&lt;/code&gt; に任せている。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# mise.toml
[tools]
go = &quot;1.23&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;現在のGoはmodulesを使うため、昔の &lt;code&gt;GOPATH&lt;/code&gt; ワークスペース配置は不要で、プロジェクトをディスク上の好きな場所に置ける。一方、&lt;code&gt;go install&lt;/code&gt; の出力先は管理したいので &lt;code&gt;GOBIN&lt;/code&gt; を設定する。私は、すでに &lt;code&gt;PATH&lt;/code&gt; に追加してある &lt;code&gt;~/.local/bin&lt;/code&gt; を使っている。&lt;a href=&quot;/ja/posts/python/&quot;&gt;Pythonの記事&lt;/a&gt;で個人用スクリプトを置いた場所と同じだ。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# 00-env.zsh
export GOBIN=&quot;$HOME/.local/bin&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;依存関係を宣言する &lt;code&gt;go.mod&lt;/code&gt; と、チェックサムを固定する &lt;code&gt;go.sum&lt;/code&gt; は両方コミットする。Goのプロジェクトには細かな生成物があまり残らない。ビルドしたバイナリを名前で除外するか、除外済みの出力ディレクトリへまとめればよい。&lt;/p&gt;
&lt;h2&gt;Ruby&lt;/h2&gt;
&lt;p&gt;Rubyには主権バージョンツールがないので、Layer 1は &lt;code&gt;mise&lt;/code&gt; に任せられる。ただし、実際にRubyを必要とするプロジェクトができたときだけインストールする。&lt;/p&gt;
&lt;p&gt;macOSに付属するシステムRubyをプロジェクトの依存関係に使ってはいけない。古いうえ、Appleも変更を推奨していない。そこへ &lt;code&gt;sudo gem install&lt;/code&gt; すると、システム環境を壊す典型的な原因になる。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# mise.toml — only when a project genuinely needs it
[tools]
ruby = &quot;3.3&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;macOSの開発者がRubyを入れる理由としてCocoaPodsは今もよくあるが、多くのSwiftプロジェクトではSPMによって不要になった。SPMで足りるなら、Rubyは入れなくてよい。これは&lt;a href=&quot;/ja/posts/maintenance/&quot;&gt;メンテナンスの記事&lt;/a&gt;で扱った、必要になってから導入する方針にも合う。システムRubyが古いからといって、RubyプロジェクトのないMacへ急いで &lt;code&gt;mise&lt;/code&gt; 管理のRubyを追加する理由にはならない。&lt;/p&gt;
&lt;p&gt;Rubyを使う場合は &lt;code&gt;Gemfile.lock&lt;/code&gt; をコミットする。Bundlerのローカルパスを &lt;code&gt;vendor/bundle/&lt;/code&gt; などに設定したなら、そのディレクトリは除外する。&lt;/p&gt;
&lt;h2&gt;言語が変わっても同じこと&lt;/h2&gt;
&lt;p&gt;ランタイムのバージョンは、各開発者のグローバルな初期値ではなく、リポジトリ側で宣言する。&lt;code&gt;mise.toml&lt;/code&gt; または &lt;code&gt;.tool-versions&lt;/code&gt; をコミットしておけば、新しくcloneした環境でも &lt;code&gt;mise install&lt;/code&gt; で必要なバージョンを用意できる。グローバルな初期値は一時的な作業用として残せる。&lt;/p&gt;
&lt;p&gt;ロックファイルはgitに入れ、派生物は入れない。&lt;code&gt;pnpm-lock.yaml&lt;/code&gt;、&lt;code&gt;Cargo.lock&lt;/code&gt;、&lt;code&gt;go.sum&lt;/code&gt;、&lt;code&gt;Package.resolved&lt;/code&gt;、&lt;code&gt;uv.lock&lt;/code&gt;、&lt;code&gt;Gemfile.lock&lt;/code&gt; をコミットし、&lt;code&gt;node_modules&lt;/code&gt;、&lt;code&gt;target/&lt;/code&gt;、&lt;code&gt;DerivedData/&lt;/code&gt;、&lt;code&gt;.venv&lt;/code&gt; などは &lt;code&gt;.gitignore&lt;/code&gt; に入れる。&lt;/p&gt;
&lt;p&gt;一つの &lt;code&gt;mise.toml&lt;/code&gt; に &lt;code&gt;node&lt;/code&gt;、&lt;code&gt;python&lt;/code&gt;、&lt;code&gt;go&lt;/code&gt; をまとめて書ける。多言語プロジェクトだからといって、言語ごとにランタイム設定を分ける必要はない。一つのコマンドで全体を用意でき、実際の管理を一部の主権ツールへ委譲する場合にも、統一された入口には意味がある。&lt;/p&gt;
&lt;p&gt;同じ事実を複数の層で重複宣言してはいけない。ただし、似て見える設定が別の意味を持つことはある。Pythonでは、&lt;code&gt;pyproject.toml&lt;/code&gt; の &lt;code&gt;requires-python&lt;/code&gt; はコードが対応するバージョン範囲を示し、&lt;code&gt;mise.toml&lt;/code&gt; はそのマシンで使う一つのバージョンを選ぶ。両方を残すのは重複ではなく、まとめると情報が失われる。本当の重複は一つの事実に二つの情報源を作ってしまう。これは&lt;a href=&quot;/ja/posts/manifesto/&quot;&gt;シリーズ最初の記事&lt;/a&gt;で避けようとした問題そのものだ。&lt;/p&gt;
&lt;p&gt;このモデルが適用できない領域もある。CとC++には「Cのバージョン」を所有する同種のツールがなく、システムコンパイラ、SDK、さまざまなビルドシステムが層を曖昧にする。私は無理に同じ形へ押し込めない。ネイティブ拡張やビルド依存として現れた場合は、それを必要とした上位のツールに処理を任せている。&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;/ja/posts/maintenance/&quot;&gt;シリーズ最後の記事&lt;/a&gt;では、この設定を終えた後、メンテナンスそのものを新しいプロジェクトにせず、環境を使える状態に保つ方法を扱う。&lt;/p&gt;
</content:encoded></item><item><title>公式ツールに任せるべきタイミング</title><link>https://www.shiinayane.com/ja/posts/sovereignty/</link><guid isPermaLink="true">https://www.shiinayane.com/ja/posts/sovereignty/</guid><description>すべての言語を mise に預ける必要はない。ランタイムのバージョンを誰が管理するかは、その言語に十分強力な公式ツールがあるかで決める。</description><pubDate>Sat, 30 May 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;&lt;a href=&quot;/ja/posts/python/&quot;&gt;前の記事&lt;/a&gt;では、Python のバージョンは &lt;code&gt;mise&lt;/code&gt; だけに管理させ、&lt;code&gt;uv&lt;/code&gt; は依存関係のレイヤーに留めた。一方、同じ Mac の Rust は &lt;code&gt;rustup&lt;/code&gt; に任せている。&lt;code&gt;mise&lt;/code&gt; でも Rust をインストールできるのに、である。&lt;/p&gt;
&lt;p&gt;Rust だけを例外扱いしているわけではない。Layer 1、つまりランタイムのバージョンを誰に任せるか決めるとき、まず確認するのは一つだ。&lt;strong&gt;その言語には、十分に強力な公式の主権ツールがあるか。&lt;/strong&gt;&lt;/p&gt;
&lt;h2&gt;主権ツールと呼べる条件&lt;/h2&gt;
&lt;p&gt;ここでいう&lt;strong&gt;主権ツール&lt;/strong&gt;とは、言語プロジェクト自身が提供するバージョン兼ツールチェーン管理ツールのことだ。Rust の &lt;code&gt;rustup&lt;/code&gt; と Swift の Xcode が分かりやすい。強力なものが存在するなら、そのレイヤーは公式ツールに任せる。存在しない、あるいは機能が足りない場合に &lt;code&gt;mise&lt;/code&gt; で穴を埋める。&lt;/p&gt;
&lt;p&gt;ただし、公式というだけでは足りない。実際には次の四点を見る。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;言語プロジェクトの一部であること。&lt;/strong&gt; コンパイラと同じ提供元から出ており、言語の変化を後追いするサードパーティ製ツールではない。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;バージョン切り替え以外も管理すること。&lt;/strong&gt; ツールチェーンの構成要素、stable/beta/nightly のリリースチャンネル、クロスコンパイル先、エコシステム全体で通用するプロジェクト単位の固定などが該当する。単に有効なバージョンを選ぶだけなら、&lt;code&gt;mise&lt;/code&gt; がすでに得意としている。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;コミュニティーの合意があること。&lt;/strong&gt; 「この言語はどうインストールするのか」に、退屈なほど共通した答えがあるのが理想だ。三つの方式が競合しているなら、まだ主権ツールはない。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;アップグレード後も既存プロジェクトを壊さないこと。&lt;/strong&gt; 一つのレイヤーを何年も任せるには、現在の機能だけでなく契約の安定性が必要になる。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;code&gt;rustup&lt;/code&gt; は四条件をすべて満たす。Rust プロジェクトの一部であり、ツールチェーン、ターゲット、リリースチャンネルを管理し、標準のインストール手段として定着し、長年にわたって安定した契約を保ってきた。対照的なのが Go 公式の &lt;code&gt;dl&lt;/code&gt; インストーラーだ。特定バージョンの Go は取得できるが、それ以上の管理はほとんど担わず、バージョン管理の標準にもなっていない。公式ではあっても条件 2 と 3 を満たさないため、私は弱いツールと判断している。&lt;/p&gt;
&lt;p&gt;&amp;lt;figure class=&quot;my-6&quot;&amp;gt;
&amp;lt;svg viewBox=&quot;0 0 600 300&quot; role=&quot;img&quot; aria-labelledby=&quot;diagram-sov-title&quot; style=&quot;width:100%;height:auto;color:inherit&quot;&amp;gt;
&amp;lt;title id=&quot;diagram-sov-title&quot;&amp;gt;言語に強力な公式の主権ツールがあるかを判断するフロー&amp;lt;/title&amp;gt;
&amp;lt;g font-family=&quot;ui-sans-serif, system-ui, sans-serif&quot;&amp;gt;
&amp;lt;!-- root --&amp;gt;
&amp;lt;rect x=&quot;140&quot; y=&quot;20&quot; width=&quot;320&quot; height=&quot;58&quot; rx=&quot;10&quot; fill=&quot;currentColor&quot; fill-opacity=&quot;0.05&quot; stroke=&quot;currentColor&quot; stroke-opacity=&quot;0.2&quot;/&amp;gt;
&amp;lt;text x=&quot;300&quot; y=&quot;45&quot; font-size=&quot;13.5&quot; font-weight=&quot;600&quot; text-anchor=&quot;middle&quot; fill=&quot;currentColor&quot;&amp;gt;強力な公式の主権ツールがある？&amp;lt;/text&amp;gt;
&amp;lt;text x=&quot;300&quot; y=&quot;65&quot; font-size=&quot;11.5&quot; text-anchor=&quot;middle&quot; fill=&quot;currentColor&quot; fill-opacity=&quot;0.55&quot;&amp;gt;言語公式 · ツールチェーン管理 · 合意 · 安定性&amp;lt;/text&amp;gt;
&amp;lt;!-- branch labels --&amp;gt;
&amp;lt;text x=&quot;150&quot; y=&quot;108&quot; font-size=&quot;12&quot; font-weight=&quot;700&quot; text-anchor=&quot;middle&quot; fill=&quot;var(--primary)&quot;&amp;gt;ある&amp;lt;/text&amp;gt;
&amp;lt;text x=&quot;450&quot; y=&quot;108&quot; font-size=&quot;12&quot; font-weight=&quot;700&quot; text-anchor=&quot;middle&quot; fill=&quot;var(--primary)&quot;&amp;gt;ない / 弱い&amp;lt;/text&amp;gt;
&amp;lt;!-- connectors --&amp;gt;
&amp;lt;path d=&quot;M260 78 L150 120&quot; stroke=&quot;currentColor&quot; stroke-opacity=&quot;0.3&quot; fill=&quot;none&quot;/&amp;gt;
&amp;lt;path d=&quot;M340 78 L450 120&quot; stroke=&quot;currentColor&quot; stroke-opacity=&quot;0.3&quot; fill=&quot;none&quot;/&amp;gt;
&amp;lt;!-- leaf left --&amp;gt;
&amp;lt;rect x=&quot;30&quot; y=&quot;125&quot; width=&quot;240&quot; height=&quot;150&quot; rx=&quot;10&quot; fill=&quot;currentColor&quot; fill-opacity=&quot;0.05&quot; stroke=&quot;currentColor&quot; stroke-opacity=&quot;0.15&quot;/&amp;gt;
&amp;lt;text x=&quot;150&quot; y=&quot;152&quot; font-size=&quot;13&quot; font-weight=&quot;600&quot; text-anchor=&quot;middle&quot; fill=&quot;currentColor&quot;&amp;gt;主権ツールに任せる&amp;lt;/text&amp;gt;
&amp;lt;text x=&quot;150&quot; y=&quot;176&quot; font-size=&quot;12&quot; text-anchor=&quot;middle&quot; fill=&quot;currentColor&quot; fill-opacity=&quot;0.6&quot;&amp;gt;Layer 1 を所有&amp;lt;/text&amp;gt;
&amp;lt;text x=&quot;150&quot; y=&quot;210&quot; font-size=&quot;12.5&quot; text-anchor=&quot;middle&quot; fill=&quot;currentColor&quot; fill-opacity=&quot;0.7&quot;&amp;gt;rustup  →  Rust&amp;lt;/text&amp;gt;
&amp;lt;text x=&quot;150&quot; y=&quot;232&quot; font-size=&quot;12.5&quot; text-anchor=&quot;middle&quot; fill=&quot;currentColor&quot; fill-opacity=&quot;0.7&quot;&amp;gt;Xcode  →  Swift&amp;lt;/text&amp;gt;
&amp;lt;!-- leaf right --&amp;gt;
&amp;lt;rect x=&quot;330&quot; y=&quot;125&quot; width=&quot;240&quot; height=&quot;150&quot; rx=&quot;10&quot; fill=&quot;currentColor&quot; fill-opacity=&quot;0.05&quot; stroke=&quot;currentColor&quot; stroke-opacity=&quot;0.15&quot;/&amp;gt;
&amp;lt;text x=&quot;450&quot; y=&quot;152&quot; font-size=&quot;13&quot; font-weight=&quot;600&quot; text-anchor=&quot;middle&quot; fill=&quot;currentColor&quot;&amp;gt;mise で補う&amp;lt;/text&amp;gt;
&amp;lt;text x=&quot;450&quot; y=&quot;176&quot; font-size=&quot;12&quot; text-anchor=&quot;middle&quot; fill=&quot;currentColor&quot; fill-opacity=&quot;0.6&quot;&amp;gt;mise が Layer 1 を所有&amp;lt;/text&amp;gt;
&amp;lt;text x=&quot;450&quot; y=&quot;208&quot; font-size=&quot;12.5&quot; text-anchor=&quot;middle&quot; fill=&quot;currentColor&quot; fill-opacity=&quot;0.7&quot;&amp;gt;Python · Node · Java&amp;lt;/text&amp;gt;
&amp;lt;text x=&quot;450&quot; y=&quot;230&quot; font-size=&quot;12.5&quot; text-anchor=&quot;middle&quot; fill=&quot;currentColor&quot; fill-opacity=&quot;0.7&quot;&amp;gt;Ruby · Go（弱い）&amp;lt;/text&amp;gt;
&amp;lt;/g&amp;gt;
&amp;lt;/svg&amp;gt;
&amp;lt;/figure&amp;gt;&lt;/p&gt;
&lt;h2&gt;自分が使う言語に当てはめる&lt;/h2&gt;
&lt;p&gt;以下の判断はすべて Layer 1、つまりランタイムのバージョンに限った話である。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;**Python：**主権ツールはない。&lt;code&gt;pyenv&lt;/code&gt;、&lt;code&gt;mise&lt;/code&gt;、&lt;code&gt;uv&lt;/code&gt;、システムの Python などが併存している。そのため Layer 1 は &lt;code&gt;mise&lt;/code&gt;、Layer 2 は &lt;code&gt;uv&lt;/code&gt; に任せている。構成は &lt;a href=&quot;/ja/posts/python/&quot;&gt;Python の記事&lt;/a&gt;で説明した。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Rust：&lt;/strong&gt;&lt;code&gt;rustup&lt;/code&gt; が主権ツールである。私の Mac では &lt;code&gt;rustup&lt;/code&gt; が Rust をインストールし、&lt;code&gt;mise&lt;/code&gt; には任せない。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Node.js：&lt;/strong&gt;&lt;code&gt;nvm&lt;/code&gt;、&lt;code&gt;fnm&lt;/code&gt;、&lt;code&gt;volta&lt;/code&gt;、&lt;code&gt;mise&lt;/code&gt; が競合し、公式の答えはない。私は &lt;code&gt;mise&lt;/code&gt; を使う。&lt;/li&gt;
&lt;li&gt;**Java：**主権ツールがなく、さらに複数のディストリビューションがある。私は &lt;code&gt;mise&lt;/code&gt; を使い、デフォルトは Temurin にしている。プロジェクトごとにディストリビューション選びをやり直さずに済む、無難で中立的な選択だと思う。&lt;/li&gt;
&lt;li&gt;**Swift / iOS：**Apple が提供する Xcode がツールチェーン、SDK、ビルドシステムをまとめて所有している。macOS で別の管理ツールから Swift を入れるのはプラットフォームに逆らうようなものなので、完全に Xcode に任せる。&lt;/li&gt;
&lt;li&gt;**Go：**公式の選択肢はこのレイヤーを任せるには弱い。多くの利用者と同様、Go は &lt;code&gt;mise&lt;/code&gt; に預けて問題ないと考えている。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Ruby：&lt;/strong&gt;&lt;code&gt;rbenv&lt;/code&gt;、&lt;code&gt;rvm&lt;/code&gt;、&lt;code&gt;chruby&lt;/code&gt;、&lt;code&gt;mise&lt;/code&gt; があり、主権ツールはない。Ruby が必要なときは &lt;code&gt;mise&lt;/code&gt; でバージョンを管理する。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;&lt;code&gt;mise&lt;/code&gt; はプロキシにもなれる&lt;/h2&gt;
&lt;p&gt;Rust を &lt;code&gt;rustup&lt;/code&gt; に任せても、すべての操作で &lt;code&gt;rustup&lt;/code&gt; を直接入力する必要はない。プロジェクトの &lt;code&gt;mise.toml&lt;/code&gt; に Rust を書くこともできる。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# mise.toml
[tools]
rust = &quot;1.78&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;この場合、&lt;code&gt;mise&lt;/code&gt; は Rust のツールチェーン管理を再実装せず、内部で &lt;code&gt;rustup&lt;/code&gt; を呼び出す。私はこれを &lt;strong&gt;&lt;code&gt;mise&lt;/code&gt; のプロキシ運用&lt;/strong&gt;と考えている。共通の窓口は &lt;code&gt;mise&lt;/code&gt; だが、ツールチェーンの所有者と信頼できる情報源は引き続き &lt;code&gt;rustup&lt;/code&gt; である。&lt;/p&gt;
&lt;p&gt;構成は二通り考えられる。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;**&lt;code&gt;rustup&lt;/code&gt; を直接使う：**Rust だけのプロジェクトなら、統一すべき多言語環境はない。&lt;code&gt;rustup&lt;/code&gt; を直接操作するのが簡単だ。&lt;/li&gt;
&lt;li&gt;**&lt;code&gt;mise&lt;/code&gt; をプロジェクトの窓口にする：**すでに &lt;code&gt;mise.toml&lt;/code&gt; で Python と Node を固定している多言語プロジェクトなら、&lt;code&gt;rust = &quot;1.78&quot;&lt;/code&gt; も追加できる。&lt;code&gt;mise install&lt;/code&gt; 一つでプロジェクト全体を準備しつつ、Rust の処理だけは &lt;code&gt;rustup&lt;/code&gt; に委譲できる。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;注意したいのは、同じプロジェクトで二つの運用を混ぜ、どちらのファイルが正しいのか分からなくなることだ。操作の窓口には &lt;code&gt;mise&lt;/code&gt; と &lt;code&gt;rustup&lt;/code&gt; のどちらも選べるが、Rust ツールチェーンの所有者は &lt;code&gt;rustup&lt;/code&gt; のままでなければならない。&lt;/p&gt;
&lt;h2&gt;判断は将来変わり得る&lt;/h2&gt;
&lt;p&gt;Zig を初めて調べたときも、同じ基準を使った。当時はコミュニティーが合意した強力な公式管理ツールがなかったので、&lt;code&gt;mise&lt;/code&gt; か単純な手動インストールで十分だった。Mojo も同じく、まだ若いエコシステムに当てはまる。Haskell は境界的な例で、GHCup は任せてもよいほど強力だ。Lua には合意されたツールがないため、&lt;code&gt;mise&lt;/code&gt; が穴を埋める。&lt;/p&gt;
&lt;p&gt;これは現在の判断であって、永久の割り当てではない。将来 Python が強力な公式管理ツールを出し、コミュニティーもそこへ収束したなら、私は Python を &lt;code&gt;mise&lt;/code&gt; の管理から外す。今の &lt;code&gt;rustup&lt;/code&gt; と同じ扱いに変えるはずだ。&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;/ja/posts/manifesto/&quot;&gt;シリーズ最初の記事&lt;/a&gt;では、資源の種類ごとに所有者を一つだけ置くと定義した。今回の基準は、ランタイムバージョンの所有者を決めるためのものだ。&lt;a href=&quot;/ja/posts/polyglot/&quot;&gt;次の記事&lt;/a&gt;では、この判断を実際の構成に落とし込み、各言語の落とし穴、ロックファイル、&lt;code&gt;gitignore&lt;/code&gt; パターンまで扱う。&lt;/p&gt;
</content:encoded></item><item><title>mise と uv が同じ Python を使うようにする</title><link>https://www.shiinayane.com/ja/posts/python/</link><guid isPermaLink="true">https://www.shiinayane.com/ja/posts/python/</guid><description>uv が mise の知らない Python をインストールしていた。python-preference を only-system に変えると管理元は一つに戻るが、既存プロジェクトにも影響がある。</description><pubDate>Sat, 30 May 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;同じコマンドを二つのディレクトリで実行したところ、異なるバージョンが返ってきた。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ cd ~/project &amp;amp;&amp;amp; uv run python --version
Python 3.12.7
$ cd ~ &amp;amp;&amp;amp; uv run python --version
Python 3.13.1
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;プロジェクトごとにランタイムを固定できるので、最初の結果はおかしくない。気になったのはホームディレクトリで現れた Python 3.13.1 だった。自分でインストールした覚えがなく、ランタイムのバージョン管理を任せている &lt;code&gt;mise&lt;/code&gt; もこのバージョンを認識していない。このシリーズの&lt;a href=&quot;/ja/posts/manifesto/&quot;&gt;最初の記事&lt;/a&gt;で書いた構成では、マシンに存在する Python は &lt;code&gt;mise&lt;/code&gt; だけが決めるはずだった。&lt;/p&gt;
&lt;h2&gt;もう一つの Python を探す&lt;/h2&gt;
&lt;p&gt;まず、それぞれの場面で &lt;code&gt;python&lt;/code&gt; が何を指しているか確認した。シェルと &lt;code&gt;uv run&lt;/code&gt; は別の実行ファイルを使っていた。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ which python
/Users/me/.local/share/mise/installs/python/3.12.7/bin/python
$ uv run python -c &apos;import sys; print(sys.executable)&apos;
/Users/me/.local/share/uv/python/cpython-3.13.1-macos-aarch64-none/bin/python3.13
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;シェルが見つけたのは、&lt;code&gt;mise&lt;/code&gt; がインストールして &lt;code&gt;PATH&lt;/code&gt; に置いたインタープリタだった。一方、バージョンを固定したプロジェクトの外では、&lt;code&gt;uv run&lt;/code&gt; は &lt;code&gt;~/.local/share/uv/python&lt;/code&gt; 以下を選んでいる。自分で用意した覚えのないディレクトリだ。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;uv&lt;/code&gt; がインストール済みと判断しているものを一覧にすると、二つの出所がはっきりした。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ uv python list --only-installed
cpython-3.13.1-macos-aarch64-none    /Users/me/.local/share/uv/python/cpython-3.13.1-.../bin/python3.13
cpython-3.12.7-macos-aarch64-none    /Users/me/.local/share/mise/installs/python/3.12.7/bin/python3.12
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;実際に別々の CPython ビルドが存在していた。&lt;code&gt;readlink -f&lt;/code&gt; で実体をたどっても、同じバイナリへの別名ではない。一方は &lt;code&gt;mise&lt;/code&gt; が所有し、もう一方は &lt;code&gt;uv&lt;/code&gt; がダウンロードして自身のディレクトリに保存したものだった。&lt;/p&gt;
&lt;p&gt;これは &lt;code&gt;uv&lt;/code&gt; のバグではなく、デフォルトの動作である。&lt;code&gt;python-preference&lt;/code&gt; の初期値は &lt;code&gt;managed&lt;/code&gt; で、uv 管理の Python を優先し、利用できるインタープリタがプロジェクトの &lt;code&gt;requires-python&lt;/code&gt; を満たさなければ自動でダウンロードする。ランタイムもパッケージも &lt;code&gt;uv&lt;/code&gt; に任せるなら便利だが、Python のバージョンを &lt;code&gt;mise&lt;/code&gt; だけで管理したい自分の構成とは衝突する。&lt;/p&gt;
&lt;h2&gt;インタープリタ、仮想環境、キャッシュ&lt;/h2&gt;
&lt;p&gt;設定を変える前に、「隠しディレクトリにある Python 関係のファイル」に見える三つを分けて考えた。それぞれ役割も寿命も管理元も違う。&lt;/p&gt;
&lt;p&gt;&amp;lt;figure class=&quot;my-6&quot;&amp;gt;
&amp;lt;svg viewBox=&quot;0 0 640 300&quot; role=&quot;img&quot; aria-labelledby=&quot;diagram-factory-title&quot; style=&quot;width:100%;height:auto;color:inherit&quot;&amp;gt;
&amp;lt;title id=&quot;diagram-factory-title&quot;&amp;gt;設計図、サンプル、共有ライブラリ：mise のインストール、.venv、uv のキャッシュ&amp;lt;/title&amp;gt;
&amp;lt;g font-family=&quot;ui-sans-serif, system-ui, sans-serif&quot;&amp;gt;
&amp;lt;!-- Blueprint --&amp;gt;
&amp;lt;rect x=&quot;10&quot; y=&quot;60&quot; width=&quot;180&quot; height=&quot;180&quot; rx=&quot;10&quot; fill=&quot;currentColor&quot; fill-opacity=&quot;0.05&quot; stroke=&quot;currentColor&quot; stroke-opacity=&quot;0.15&quot;/&amp;gt;
&amp;lt;text x=&quot;100&quot; y=&quot;40&quot; font-size=&quot;13&quot; font-weight=&quot;700&quot; text-anchor=&quot;middle&quot; fill=&quot;var(--primary)&quot;&amp;gt;設計図&amp;lt;/text&amp;gt;
&amp;lt;text x=&quot;100&quot; y=&quot;92&quot; font-size=&quot;13&quot; font-weight=&quot;600&quot; text-anchor=&quot;middle&quot; fill=&quot;currentColor&quot;&amp;gt;mise install&amp;lt;/text&amp;gt;
&amp;lt;text x=&quot;100&quot; y=&quot;116&quot; font-size=&quot;12&quot; text-anchor=&quot;middle&quot; fill=&quot;currentColor&quot; fill-opacity=&quot;0.6&quot;&amp;gt;python 3.14.0&amp;lt;/text&amp;gt;
&amp;lt;text x=&quot;100&quot; y=&quot;150&quot; font-size=&quot;11.5&quot; text-anchor=&quot;middle&quot; fill=&quot;currentColor&quot; fill-opacity=&quot;0.5&quot;&amp;gt;バージョンごとに&amp;lt;/text&amp;gt;
&amp;lt;text x=&quot;100&quot; y=&quot;168&quot; font-size=&quot;11.5&quot; text-anchor=&quot;middle&quot; fill=&quot;currentColor&quot; fill-opacity=&quot;0.5&quot;&amp;gt;完全なインタープリタ一式&amp;lt;/text&amp;gt;
&amp;lt;text x=&quot;100&quot; y=&quot;200&quot; font-size=&quot;11.5&quot; text-anchor=&quot;middle&quot; fill=&quot;currentColor&quot; fill-opacity=&quot;0.5&quot;&amp;gt;複数バージョンが&amp;lt;/text&amp;gt;
&amp;lt;text x=&quot;100&quot; y=&quot;218&quot; font-size=&quot;11.5&quot; text-anchor=&quot;middle&quot; fill=&quot;currentColor&quot; fill-opacity=&quot;0.5&quot;&amp;gt;共存できる&amp;lt;/text&amp;gt;
&amp;lt;!-- Sample unit --&amp;gt;
&amp;lt;rect x=&quot;230&quot; y=&quot;60&quot; width=&quot;180&quot; height=&quot;180&quot; rx=&quot;10&quot; fill=&quot;currentColor&quot; fill-opacity=&quot;0.05&quot; stroke=&quot;currentColor&quot; stroke-opacity=&quot;0.15&quot;/&amp;gt;
&amp;lt;text x=&quot;320&quot; y=&quot;40&quot; font-size=&quot;13&quot; font-weight=&quot;700&quot; text-anchor=&quot;middle&quot; fill=&quot;var(--primary)&quot;&amp;gt;サンプル&amp;lt;/text&amp;gt;
&amp;lt;text x=&quot;320&quot; y=&quot;92&quot; font-size=&quot;13&quot; font-weight=&quot;600&quot; text-anchor=&quot;middle&quot; fill=&quot;currentColor&quot;&amp;gt;project/.venv&amp;lt;/text&amp;gt;
&amp;lt;text x=&quot;320&quot; y=&quot;124&quot; font-size=&quot;11.5&quot; text-anchor=&quot;middle&quot; fill=&quot;currentColor&quot; fill-opacity=&quot;0.6&quot;&amp;gt;bin/python → シンボリックリンク&amp;lt;/text&amp;gt;
&amp;lt;text x=&quot;320&quot; y=&quot;142&quot; font-size=&quot;11.5&quot; text-anchor=&quot;middle&quot; fill=&quot;currentColor&quot; fill-opacity=&quot;0.6&quot;&amp;gt;設計図を参照&amp;lt;/text&amp;gt;
&amp;lt;text x=&quot;320&quot; y=&quot;186&quot; font-size=&quot;11.5&quot; text-anchor=&quot;middle&quot; fill=&quot;currentColor&quot; fill-opacity=&quot;0.6&quot;&amp;gt;site-packages =&amp;lt;/text&amp;gt;
&amp;lt;text x=&quot;320&quot; y=&quot;204&quot; font-size=&quot;11.5&quot; text-anchor=&quot;middle&quot; fill=&quot;currentColor&quot; fill-opacity=&quot;0.6&quot;&amp;gt;唯一のプロジェクト固有部分&amp;lt;/text&amp;gt;
&amp;lt;!-- Shared library --&amp;gt;
&amp;lt;rect x=&quot;450&quot; y=&quot;60&quot; width=&quot;180&quot; height=&quot;180&quot; rx=&quot;10&quot; fill=&quot;currentColor&quot; fill-opacity=&quot;0.05&quot; stroke=&quot;currentColor&quot; stroke-opacity=&quot;0.15&quot;/&amp;gt;
&amp;lt;text x=&quot;540&quot; y=&quot;40&quot; font-size=&quot;13&quot; font-weight=&quot;700&quot; text-anchor=&quot;middle&quot; fill=&quot;var(--primary)&quot;&amp;gt;共有ライブラリ&amp;lt;/text&amp;gt;
&amp;lt;text x=&quot;540&quot; y=&quot;92&quot; font-size=&quot;13&quot; font-weight=&quot;600&quot; text-anchor=&quot;middle&quot; fill=&quot;currentColor&quot;&amp;gt;uv cache&amp;lt;/text&amp;gt;
&amp;lt;text x=&quot;540&quot; y=&quot;124&quot; font-size=&quot;11.5&quot; text-anchor=&quot;middle&quot; fill=&quot;currentColor&quot; fill-opacity=&quot;0.6&quot;&amp;gt;コンテンツアドレス方式&amp;lt;/text&amp;gt;
&amp;lt;text x=&quot;540&quot; y=&quot;160&quot; font-size=&quot;11.5&quot; text-anchor=&quot;middle&quot; fill=&quot;currentColor&quot; fill-opacity=&quot;0.6&quot;&amp;gt;各パッケージを一度だけ保存&amp;lt;/text&amp;gt;
&amp;lt;text x=&quot;540&quot; y=&quot;178&quot; font-size=&quot;11.5&quot; text-anchor=&quot;middle&quot; fill=&quot;currentColor&quot; fill-opacity=&quot;0.6&quot;&amp;gt;すべてのプロジェクトで&amp;lt;/text&amp;gt;
&amp;lt;text x=&quot;540&quot; y=&quot;196&quot; font-size=&quot;11.5&quot; text-anchor=&quot;middle&quot; fill=&quot;currentColor&quot; fill-opacity=&quot;0.6&quot;&amp;gt;共有&amp;lt;/text&amp;gt;
&amp;lt;!-- arrows --&amp;gt;
&amp;lt;text x=&quot;210&quot; y=&quot;135&quot; font-size=&quot;18&quot; text-anchor=&quot;middle&quot; fill=&quot;currentColor&quot; fill-opacity=&quot;0.4&quot;&amp;gt;←&amp;lt;/text&amp;gt;
&amp;lt;text x=&quot;210&quot; y=&quot;152&quot; font-size=&quot;10&quot; text-anchor=&quot;middle&quot; fill=&quot;currentColor&quot; fill-opacity=&quot;0.5&quot;&amp;gt;シンボリックリンク&amp;lt;/text&amp;gt;
&amp;lt;text x=&quot;430&quot; y=&quot;135&quot; font-size=&quot;18&quot; text-anchor=&quot;middle&quot; fill=&quot;currentColor&quot; fill-opacity=&quot;0.4&quot;&amp;gt;→&amp;lt;/text&amp;gt;
&amp;lt;text x=&quot;430&quot; y=&quot;152&quot; font-size=&quot;10&quot; text-anchor=&quot;middle&quot; fill=&quot;currentColor&quot; fill-opacity=&quot;0.5&quot;&amp;gt;リンク&amp;lt;/text&amp;gt;
&amp;lt;/g&amp;gt;
&amp;lt;/svg&amp;gt;
&amp;lt;/figure&amp;gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;mise install python@3.14&lt;/code&gt; で入るバージョンは&lt;strong&gt;設計図&lt;/strong&gt;と考えると分かりやすい。バージョン番号ごとに完全なインタープリタが一つあり、&lt;code&gt;mise&lt;/code&gt; のインストールディレクトリでは複数のバージョンが共存できる。3.12.7 と 3.14.0 は互いに干渉しない二つの設計図である。&lt;/p&gt;
&lt;p&gt;プロジェクトの &lt;strong&gt;&lt;code&gt;.venv&lt;/code&gt;&lt;/strong&gt; は、そこから作る&lt;strong&gt;サンプル&lt;/strong&gt;に当たる。仮想環境の大部分はシンボリックリンクで、&lt;code&gt;bin/python&lt;/code&gt; は Python をもう一式コピーするのではなく、元のインタープリタを参照する。実際にプロジェクト固有なのは &lt;code&gt;site-packages&lt;/code&gt; だ。そのため &lt;code&gt;.venv&lt;/code&gt; は小さく、すぐ作れる。ただし &lt;code&gt;mise&lt;/code&gt; 管理の Python を削除すると、それを参照する仮想環境はすべて壊れる。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;uv のキャッシュ&lt;/strong&gt;は共有ライブラリである。コンテンツアドレス方式で、同じバージョンのパッケージを一度だけ保存し、コピーする代わりに各プロジェクトの &lt;code&gt;site-packages&lt;/code&gt; へリンクする。十個のプロジェクトが同じバージョンの &lt;code&gt;numpy&lt;/code&gt; を使うなら、キャッシュ上の一つを共有できる。何度もダウンロードして展開せず、リンクを作るだけで済むことが &lt;code&gt;uv&lt;/code&gt; の速さにつながっている。&lt;/p&gt;
&lt;p&gt;この分担なら、&lt;code&gt;mise&lt;/code&gt; がインタープリタを用意し、&lt;code&gt;uv&lt;/code&gt; がそれを使ってプロジェクト環境を作り、パッケージキャッシュから依存関係を配置する。&lt;code&gt;uv&lt;/code&gt; まで別のインタープリタを用意する必要はない。&lt;/p&gt;
&lt;h2&gt;uv をシステム Python に限定する&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;uv&lt;/code&gt; は &lt;code&gt;~/.config/uv/uv.toml&lt;/code&gt; からグローバル設定を読み込む。追加したのは一行だけだった。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# ~/.config/uv/uv.toml
python-preference = &quot;only-system&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;python-preference&lt;/code&gt; には四つの値がある。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;only-managed&lt;/code&gt; は uv 管理の Python だけを使い、システムのインタープリタを無視する。システムから最も強く分離できる一方、&lt;code&gt;mise&lt;/code&gt; が所有する構成からは最も遠い。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;managed&lt;/code&gt; はデフォルト値で、uv 管理の Python を優先し、次にシステム Python を使う。どちらも &lt;code&gt;requires-python&lt;/code&gt; を満たさなければ uv 管理のものをダウンロードする。今回の予期しないインストールはこれが原因だった。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;system&lt;/code&gt; は &lt;code&gt;PATH&lt;/code&gt; 上の Python を優先するが、適切なものがなければ uv 管理のインタープリタをダウンロードできる。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;only-system&lt;/code&gt; は &lt;code&gt;mise&lt;/code&gt; が &lt;code&gt;PATH&lt;/code&gt; に置いたものを含むシステム Python だけを使い、自動ダウンロードはしない。条件を満たすものがなければエラーになる。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;自分の環境では、最後のエラーが役に立つ。足りないバージョンは、&lt;code&gt;mise&lt;/code&gt; で明示的にインストールするまで不足したまま見える。&lt;code&gt;mise&lt;/code&gt; を優先しつつ &lt;code&gt;uv&lt;/code&gt; のフォールバックも残したい場合は、より緩い &lt;code&gt;system&lt;/code&gt; を選べばよい。&lt;/p&gt;
&lt;p&gt;設定を変更した後、&lt;code&gt;uv&lt;/code&gt; が入れた Python 3.13.1 を削除し、影響を受けた環境を作り直した。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ uv python uninstall 3.13.1
$ cd ~/project &amp;amp;&amp;amp; rm -rf .venv &amp;amp;&amp;amp; uv sync
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;これで &lt;code&gt;uv run python&lt;/code&gt; とシェルの &lt;code&gt;python&lt;/code&gt; は、どちらも &lt;code&gt;mise&lt;/code&gt; 管理のインタープリタを使うようになった。&lt;/p&gt;
&lt;h2&gt;変更後に既存プロジェクトが失敗する場合&lt;/h2&gt;
&lt;p&gt;グローバルな選択規則を変えると、以前の規則で作った環境にも影響する。古いプロジェクトの中には、&lt;code&gt;mise.toml&lt;/code&gt; で指定している Python をすでに &lt;code&gt;mise&lt;/code&gt; から削除してしまったものがあった。以前なら &lt;code&gt;uv sync&lt;/code&gt; が不足したバージョンを自動でダウンロードして先へ進めたが、&lt;code&gt;only-system&lt;/code&gt; では止まる。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;error: No interpreter found for Python 3.11 in system path
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;このエラーは、プロジェクトが要求する Python 3.11 がマシンにないという意味だ。修復も明示的に行う。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ mise install python@3.11   # provision the blueprint, deliberately
$ uv sync                    # now succeeds, using mise&apos;s Python
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;古いプロジェクトを再び開くときは、次の点を確認している。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;code&gt;pyproject.toml&lt;/code&gt; の &lt;code&gt;requires-python&lt;/code&gt; と、&lt;code&gt;mise.toml&lt;/code&gt; で固定したバージョンを読む。&lt;/li&gt;
&lt;li&gt;インタープリタがなければ &lt;code&gt;mise install&lt;/code&gt; を実行する。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;rm -rf .venv &amp;amp;&amp;amp; uv sync&lt;/code&gt; で、インストール済みのインタープリタを参照する環境に作り直す。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;uv run python --version&lt;/code&gt; で想定したバージョンか確認する。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;デフォルト設定なら暗黙に解決していた不足に対して、&lt;code&gt;mise install&lt;/code&gt; を一度実行する手間が増える。自分は不一致が見えるほうを選んだが、厳密さにもコストはある。&lt;/p&gt;
&lt;p&gt;また、&lt;a href=&quot;/ja/posts/dotfiles/&quot;&gt;dotfiles の記事&lt;/a&gt;で扱った他の設定と同じように、&lt;code&gt;uv.toml&lt;/code&gt; も chezmoi のソースリポジトリへ入れている。別のマシンでも同じ規則を再現するためだ。&lt;/p&gt;
&lt;h2&gt;小さなスクリプトならプロジェクトは要らない&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;httpx&lt;/code&gt; を使う40行程度のスクリプトのために、&lt;code&gt;pyproject.toml&lt;/code&gt;、&lt;code&gt;.venv&lt;/code&gt;、ロックファイルまで用意するのは重い。&lt;code&gt;uv&lt;/code&gt; にはもっと軽い方法があり、どちらも &lt;code&gt;only-system&lt;/code&gt; に従う。&lt;/p&gt;
&lt;p&gt;一度だけ実行するなら、&lt;code&gt;--with&lt;/code&gt; で一時環境に依存パッケージを追加できる。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ uv run --with httpx --with rich script.py
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;残しておきたいスクリプトには PEP 723 のメタデータを使い、必要な Python と依存関係をファイル内に記録できる。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# /// script
# requires-python = &quot;&amp;gt;=3.12&quot;
# dependencies = [&quot;httpx&quot;, &quot;rich&quot;]
# ///
import httpx
from rich import print
print(httpx.get(&quot;https://example.com&quot;).status_code)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;uv run script.py&lt;/code&gt; を実行すると、&lt;code&gt;uv&lt;/code&gt; はこのメタデータを読み、キャッシュから環境を組み立ててスクリプトを起動する。プロジェクト用の &lt;code&gt;.venv&lt;/code&gt; は要らない。さらに &lt;code&gt;uv&lt;/code&gt; の shebang を付けて &lt;code&gt;chmod +x&lt;/code&gt; を実行し、&lt;code&gt;~/.local/bin&lt;/code&gt; などへ置けば、依存関係をファイル内に残したまま通常の &lt;code&gt;PATH&lt;/code&gt; 上のコマンドとして使える。以前は個人用ツールを &lt;code&gt;pip install&lt;/code&gt; でグローバルなインタープリタへ入れ、後から存在を忘れることが多かったので、こちらのほうが扱いやすい。&lt;/p&gt;
&lt;p&gt;Python 製のコマンドラインツールは、それぞれ分離されたツール環境に置く。継続してインストールするなら &lt;code&gt;uv tool install&lt;/code&gt;、一度だけ実行するなら &lt;code&gt;uvx&lt;/code&gt; が使える。&lt;code&gt;ruff&lt;/code&gt; や &lt;code&gt;httpie&lt;/code&gt; の置き場所として、グローバルな &lt;code&gt;pip install&lt;/code&gt; より適している。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;mise&lt;/code&gt; を使わないなら、Python とパッケージの両方を &lt;code&gt;uv&lt;/code&gt; に任せても一貫しており、デフォルトの &lt;code&gt;managed&lt;/code&gt; がそのまま合うこともある。今回の問題は、二つのツールが同時にランタイムを決めていたことだった。&lt;a href=&quot;/ja/posts/sovereignty/&quot;&gt;次の記事&lt;/a&gt;では、どんな場合にその層を公式ツールへ任せるかを扱う。&lt;/p&gt;
</content:encoded></item><item><title>Brewfile の妥協：Mac アプリは結果整合性で管理する</title><link>https://www.shiinayane.com/ja/posts/apps/</link><guid isPermaLink="true">https://www.shiinayane.com/ja/posts/apps/</guid><description>すべてのレイヤーを厳密に宣言管理する必要はない。差分が見えるなら、アプリのレイヤーは結果整合性で十分だった。</description><pubDate>Sat, 30 May 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;私の Brewfile は、Mac に実際に入っているアプリと少しずれていることが多い。それで構わないと思っている。&lt;/p&gt;
&lt;p&gt;これは、環境を一つの管理元で厳密に制御した &lt;a href=&quot;/ja/posts/python/&quot;&gt;Python の記事&lt;/a&gt;や、散らばった設定ファイルまで管理対象にした &lt;a href=&quot;/ja/posts/dotfiles/&quot;&gt;dotfiles の記事&lt;/a&gt;より緩い運用だ。ただし、アプリで困る場面はそれらと違う。同じ強さで管理しても割に合わない。&lt;/p&gt;
&lt;h2&gt;このレイヤーでは差分を許している&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;レイヤー 3——プロジェクトの依存関係&lt;/strong&gt;には厳密な整合性が必要になる。lockfile が指定するバージョンと開発環境の実物が違えば、ビルドが失敗するかもしれない。手元だけ動いて CI では挙動が変わることもある。影響はすぐに現れ、ときには他の人まで巻き込むため、バージョンの固定や再現可能なインストールに手間をかける価値がある。&lt;/p&gt;
&lt;p&gt;一方の &lt;strong&gt;レイヤー 0——システム&lt;/strong&gt;には、Homebrew で入れたアプリやコマンドラインツールがある。今日 &lt;code&gt;brew install&lt;/code&gt; を実行して Brewfile への追記を忘れても、今使っている Mac はそのまま動く。たいてい気づくのは、後日 Brewfile から新しい環境を復元したときだ。足りないツールをインストールし、忘れていた一行を追加すれば済む。&lt;/p&gt;
&lt;p&gt;コストはゼロではないものの、発生するのは後で、しかも通常は小さい。そのため、普段の差分は許し、定期的に照合して収束させている。プロジェクト依存関係のような強い整合性ではなく、ここでは&lt;strong&gt;結果整合性&lt;/strong&gt;を使う。&lt;/p&gt;
&lt;h2&gt;インストール元の選び方&lt;/h2&gt;
&lt;p&gt;macOS では、次の順番でインストール元を選んでいる。&lt;/p&gt;
&lt;p&gt;&amp;lt;figure class=&quot;my-6&quot;&amp;gt;
&amp;lt;svg viewBox=&quot;0 0 600 120&quot; role=&quot;img&quot; aria-labelledby=&quot;diagram-channels-title&quot; style=&quot;width:100%;height:auto;color:inherit&quot;&amp;gt;
&amp;lt;title id=&quot;diagram-channels-title&quot;&amp;gt;Install channel priority: brew, then mas, then dmg&amp;lt;/title&amp;gt;
&amp;lt;g font-family=&quot;ui-sans-serif, system-ui, sans-serif&quot;&amp;gt;
&amp;lt;rect x=&quot;20&quot; y=&quot;35&quot; width=&quot;150&quot; height=&quot;50&quot; rx=&quot;10&quot; fill=&quot;currentColor&quot; fill-opacity=&quot;0.08&quot; stroke=&quot;currentColor&quot; stroke-opacity=&quot;0.2&quot;/&amp;gt;
&amp;lt;text x=&quot;95&quot; y=&quot;58&quot; font-size=&quot;13&quot; font-weight=&quot;700&quot; text-anchor=&quot;middle&quot; fill=&quot;var(--primary)&quot;&amp;gt;brew&amp;lt;/text&amp;gt;
&amp;lt;text x=&quot;95&quot; y=&quot;75&quot; font-size=&quot;11&quot; text-anchor=&quot;middle&quot; fill=&quot;currentColor&quot; fill-opacity=&quot;0.6&quot;&amp;gt;formula / cask&amp;lt;/text&amp;gt;
&amp;lt;text x=&quot;200&quot; y=&quot;65&quot; font-size=&quot;16&quot; text-anchor=&quot;middle&quot; fill=&quot;currentColor&quot; fill-opacity=&quot;0.4&quot;&amp;gt;→&amp;lt;/text&amp;gt;
&amp;lt;rect x=&quot;225&quot; y=&quot;35&quot; width=&quot;150&quot; height=&quot;50&quot; rx=&quot;10&quot; fill=&quot;currentColor&quot; fill-opacity=&quot;0.05&quot; stroke=&quot;currentColor&quot; stroke-opacity=&quot;0.15&quot;/&amp;gt;
&amp;lt;text x=&quot;300&quot; y=&quot;58&quot; font-size=&quot;13&quot; font-weight=&quot;700&quot; text-anchor=&quot;middle&quot; fill=&quot;currentColor&quot;&amp;gt;mas&amp;lt;/text&amp;gt;
&amp;lt;text x=&quot;300&quot; y=&quot;75&quot; font-size=&quot;11&quot; text-anchor=&quot;middle&quot; fill=&quot;currentColor&quot; fill-opacity=&quot;0.6&quot;&amp;gt;App Store&amp;lt;/text&amp;gt;
&amp;lt;text x=&quot;405&quot; y=&quot;65&quot; font-size=&quot;16&quot; text-anchor=&quot;middle&quot; fill=&quot;currentColor&quot; fill-opacity=&quot;0.4&quot;&amp;gt;→&amp;lt;/text&amp;gt;
&amp;lt;rect x=&quot;430&quot; y=&quot;35&quot; width=&quot;150&quot; height=&quot;50&quot; rx=&quot;10&quot; fill=&quot;currentColor&quot; fill-opacity=&quot;0.04&quot; stroke=&quot;currentColor&quot; stroke-opacity=&quot;0.12&quot;/&amp;gt;
&amp;lt;text x=&quot;505&quot; y=&quot;58&quot; font-size=&quot;13&quot; font-weight=&quot;700&quot; text-anchor=&quot;middle&quot; fill=&quot;currentColor&quot;&amp;gt;dmg&amp;lt;/text&amp;gt;
&amp;lt;text x=&quot;505&quot; y=&quot;75&quot; font-size=&quot;11&quot; text-anchor=&quot;middle&quot; fill=&quot;currentColor&quot; fill-opacity=&quot;0.6&quot;&amp;gt;last resort&amp;lt;/text&amp;gt;
&amp;lt;/g&amp;gt;
&amp;lt;/svg&amp;gt;
&amp;lt;/figure&amp;gt;&lt;/p&gt;
&lt;p&gt;最初に探すのは &lt;strong&gt;Homebrew&lt;/strong&gt; だ。コマンドラインツールなら formula、GUI アプリなら cask を使う。この構成では Homebrew がすでにレイヤー 0 の管理元であり、どちらの &lt;code&gt;brew&lt;/code&gt; インストールも Brewfile に簡単に記録できる。&lt;/p&gt;
&lt;p&gt;次が &lt;code&gt;mas-cli&lt;/code&gt; 経由の &lt;strong&gt;Mac App Store&lt;/strong&gt;。iCloud 同期、ファミリー共有、App Store のレシートに依存するアプリ、またはストアでしか配布されていないアプリは &lt;code&gt;mas&lt;/code&gt; から入れる。それ以外は cask のほうが扱いやすい。他のツールと同じ &lt;code&gt;brew&lt;/code&gt; コマンドで更新でき、App Store にサインインしたアカウントにも依存しないからだ。&lt;/p&gt;
&lt;p&gt;ベンダーサイトから配布される &lt;strong&gt;&lt;code&gt;.dmg&lt;/code&gt; は最後の手段&lt;/strong&gt;になる。Brewfile には書けないため、アプリ名と入手元を &lt;code&gt;manual-installs.md&lt;/code&gt; に残している。これがなければ、Brewfile で管理できていないものは自分の記憶にしか残らない。&lt;/p&gt;
&lt;h2&gt;目標の状態は一つのファイルに置く&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;mas-cli&lt;/code&gt; を使うと、App Store アプリの &lt;code&gt;mas&lt;/code&gt; エントリも formula や cask と同じ Brewfile に置ける。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# Brewfile
tap &quot;homebrew/bundle&quot;

brew &quot;ripgrep&quot;
brew &quot;mas&quot;

cask &quot;visual-studio-code&quot;
cask &quot;rectangle&quot;

# App Store apps, by their numeric ID (mas list to find them)
mas &quot;Things 3&quot;, id: 904280696
mas &quot;Xcode&quot;, id: 497799835
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;brew bundle&lt;/code&gt; を一度実行すれば、formula、cask、App Store アプリをまとめてインストールできる。&lt;a href=&quot;/ja/posts/manifesto/&quot;&gt;シリーズ最初の記事&lt;/a&gt;で扱った所有関係と同じく、この Mac に何を入れるべきかは一つのファイルで宣言する。実際の Mac より一時的に遅れることはあっても、宣言された目標状態が複数になるわけではない。&lt;/p&gt;
&lt;h2&gt;定期的に差分を確認する&lt;/h2&gt;
&lt;p&gt;結果整合性と呼ぶなら、いつか本当に収束させる必要がある。私はまず &lt;code&gt;brewdiff&lt;/code&gt; という読み取り専用のヘルスチェックを使う。インストール済みだが未宣言のものと、宣言済みだが未インストールのものを両方表示する関数だ。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# 30-functions.zsh — show drift between the Brewfile and the machine
brewdiff() {
  local brewfile=&quot;${HOMEBREW_BUNDLE_FILE:-$HOME/.config/homebrew/Brewfile}&quot;
  echo &quot;== Installed but NOT in Brewfile (undeclared) ==&quot;
  brew bundle cleanup --file=&quot;$brewfile&quot; 2&amp;gt;/dev/null \
    | grep -E &apos;^(Would uninstall|brew|cask|mas)&apos; || echo &quot;  (none)&quot;
  echo
  echo &quot;== In Brewfile but NOT installed (missing) ==&quot;
  brew bundle check --file=&quot;$brewfile&quot; --verbose 2&amp;gt;/dev/null \
    | grep -v &apos;^The Brewfile&apos; || echo &quot;  (all installed)&quot;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;この関数は表示するだけで、Mac の状態を変更しない。未宣言の項目を一つずつ見ながら、残すつもりだったのに書き忘れたツールなのか、削除してよい実験用のツールなのかを判断できる。&lt;/p&gt;
&lt;p&gt;通常の formula には &lt;code&gt;brewadd&lt;/code&gt; を使う。パッケージをインストールし、成功したら同じ操作の中で Brewfile に追記するための短い関数だ。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# install AND declare in one step
brewadd() {
  local brewfile=&quot;${HOMEBREW_BUNDLE_FILE:-$HOME/.config/homebrew/Brewfile}&quot;
  brew install &quot;$@&quot; || return 1
  for pkg in &quot;$@&quot;; do
    grep -q &quot;\&quot;$pkg\&quot;&quot; &quot;$brewfile&quot; || echo &quot;brew \&quot;$pkg\&quot;&quot; &amp;gt;&amp;gt; &quot;$brewfile&quot;
  done
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;コマンドを用意しても、実行を忘れれば意味がない。そこで shell の起動時にタイムスタンプの古さを調べ、30 日を超えていたら知らせるようにした。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# remind me to reconcile if it has been &amp;gt; 30 days
_brewdiff_reminder() {
  local stamp=&quot;$HOME/.cache/brewdiff-last&quot;
  if [[ ! -f &quot;$stamp&quot; ]] || \
     [[ $(find &quot;$stamp&quot; -mtime +30 2&amp;gt;/dev/null) ]]; then
    print -P &quot;%F{yellow}brewdiff:%f it&apos;s been a while — run &apos;brewdiff&apos; to reconcile&quot;
  fi
}
_brewdiff_reminder
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;brewdiff&lt;/code&gt; の実行後には、最後にタイムスタンプを &lt;code&gt;touch&lt;/code&gt; して時計をリセットする。月に一度の確認を先延ばしにしないよう、仕組みは意図的にこれ以上複雑にしていない。&lt;/p&gt;
&lt;p&gt;Brewfile の適用には、&lt;a href=&quot;/ja/posts/dotfiles/&quot;&gt;dotfiles の記事&lt;/a&gt;で使った chezmoi の構成をそのまま使える。&lt;code&gt;run_onchange_&lt;/code&gt; スクリプトによって、新しい Mac へのインストール時と Brewfile の内容が変わったときに &lt;code&gt;brew bundle&lt;/code&gt; を実行する。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# run_onchange_brew-bundle.sh.tmpl
#!/bin/sh
# Brewfile hash: {{ include &quot;dot_config/homebrew/Brewfile&quot; | sha256sum }}
brew bundle --file=&quot;$HOME/.config/homebrew/Brewfile&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;スクリプトを起動する仕掛けは、コメント内のハッシュにある。Brewfile のチェックサムが変わるとこの行も変わるため、chezmoi は編集後にコマンドを再実行する。&lt;/p&gt;
&lt;h2&gt;AI には監査だけを頼む&lt;/h2&gt;
&lt;p&gt;月次確認では、&lt;code&gt;brewdiff&lt;/code&gt; の出力を AI agent に渡すこともある。未宣言のパッケージが何なのかを説明し、残すか削除するかを理由付きで提案してもらう。ただし、Brewfile 自体は編集させない。&lt;/p&gt;
&lt;p&gt;たとえば「&lt;code&gt;pngquant&lt;/code&gt; は画像圧縮ツールで、一度しか使っていないように見えるので削除を検討してよい」と分かれば、調査時間を減らしつつ最終判断は自分に残せる。agent が直接ファイルを書き換えると、情報源を変更できる主体が二つになり、Brewfile は私の意図だけを表すものではなくなる。任せているのは判断材料の整理であって、変更する権限ではない。&lt;/p&gt;
&lt;h2&gt;自動化しないコマンド&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;brew bundle cleanup&lt;/code&gt; と &lt;code&gt;--cleanup&lt;/code&gt; フラグは、Brewfile にないものをすべてアンインストールする。宣言が完成した後なら便利だが、普段使うツールの半分がまだ未宣言なら危険だ。&lt;code&gt;brewdiff&lt;/code&gt; の未宣言リストが短くなり、各項目を自分で把握できるまでは &lt;code&gt;--force&lt;/code&gt; を付けて実行しない。&lt;/p&gt;
&lt;p&gt;この月次確認は、&lt;a href=&quot;/ja/posts/maintenance/&quot;&gt;メンテナンスの記事&lt;/a&gt;に書いた習慣の一例でもある。差分を見えるようにし、内容を確認してから、決めた周期で収束させる。Mac アプリについてはこれで十分だった。インストールのたびに Brewfile を即座に更新する必要はないが、書き忘れたものは見える状態にし、後から小さな手間で直せるようにしておきたい。&lt;/p&gt;
</content:encoded></item><item><title>~/.zshrc を置かない dotfiles：ZDOTDIR と chezmoi</title><link>https://www.shiinayane.com/ja/posts/dotfiles/</link><guid isPermaLink="true">https://www.shiinayane.com/ja/posts/dotfiles/</guid><description>ZDOTDIR で zsh 設定をホームディレクトリから移し、分割した設定を chezmoi で管理する。</description><pubDate>Sat, 30 May 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;長く使った &lt;code&gt;~/.zshrc&lt;/code&gt; を見ると、自分で書いた行とインストーラーが追加した行を区別できなくなっていた。&lt;/p&gt;
&lt;p&gt;インストールスクリプトは簡単に初期化処理を追記するが、ツールを消してもその行は残る。ファイルが一応動いているので触りづらくなり、さらに追記だけが増えていく。&lt;/p&gt;
&lt;p&gt;今はホームディレクトリに &lt;code&gt;~/.zshenv&lt;/code&gt; だけを残し、ほかの zsh 設定を &lt;code&gt;~/.config/zsh/&lt;/code&gt; に置いて chezmoi で管理している。&lt;a href=&quot;/ja/posts/manifesto/&quot;&gt;最初の記事&lt;/a&gt;で書いたレイヤー分けを dotfiles に適用した形だ。&lt;/p&gt;
&lt;h2&gt;ZDOTDIR で設定を移動する&lt;/h2&gt;
&lt;p&gt;macOS の interactive login shell は、おおむね次の順で設定を読む。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;/etc/zshenv      →  ~/.zshenv      (always, every shell)
/etc/zprofile    →  ~/.zprofile    (login shells)
/etc/zshrc       →  ~/.zshrc       (interactive shells)
/etc/zlogin      →  ~/.zlogin      (login shells)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;~/.zshenv&lt;/code&gt; はすべての zsh で最初に読まれる。ここで &lt;code&gt;ZDOTDIR&lt;/code&gt; を設定すると、&lt;code&gt;.zprofile&lt;/code&gt;、&lt;code&gt;.zshrc&lt;/code&gt;、&lt;code&gt;.zlogin&lt;/code&gt; の探索先が &lt;code&gt;$HOME&lt;/code&gt; から &lt;code&gt;$ZDOTDIR&lt;/code&gt; に変わる。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# ~/.zshenv — the only zsh file allowed to live in $HOME
export ZDOTDIR=&quot;${XDG_CONFIG_HOME:-$HOME/.config}/zsh&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;lt;figure class=&quot;my-6&quot;&amp;gt;
&amp;lt;svg viewBox=&quot;0 0 600 300&quot; role=&quot;img&quot; aria-labelledby=&quot;diagram-home-title-ja&quot; style=&quot;width:100%;height:auto;color:inherit&quot;&amp;gt;
&amp;lt;title id=&quot;diagram-home-title-ja&quot;&amp;gt;ZDOTDIR 使用前後のホームディレクトリ&amp;lt;/title&amp;gt;
&amp;lt;g font-family=&quot;ui-monospace, SFMono-Regular, Menlo, monospace&quot;&amp;gt;
&amp;lt;text x=&quot;20&quot; y=&quot;28&quot; font-size=&quot;13&quot; font-weight=&quot;700&quot; font-family=&quot;ui-sans-serif, system-ui, sans-serif&quot; fill=&quot;currentColor&quot;&amp;gt;Before&amp;lt;/text&amp;gt;
&amp;lt;rect x=&quot;20&quot; y=&quot;40&quot; width=&quot;250&quot; height=&quot;240&quot; rx=&quot;10&quot; fill=&quot;currentColor&quot; fill-opacity=&quot;0.05&quot; stroke=&quot;currentColor&quot; stroke-opacity=&quot;0.15&quot;/&amp;gt;
&amp;lt;text x=&quot;36&quot; y=&quot;66&quot; font-size=&quot;13&quot; fill=&quot;currentColor&quot; fill-opacity=&quot;0.85&quot;&amp;gt;~/&amp;lt;/text&amp;gt;
&amp;lt;g font-size=&quot;12.5&quot; fill=&quot;currentColor&quot; fill-opacity=&quot;0.6&quot;&amp;gt;
&amp;lt;text x=&quot;48&quot; y=&quot;90&quot;&amp;gt;.zshenv&amp;lt;/text&amp;gt;&amp;lt;text x=&quot;48&quot; y=&quot;110&quot;&amp;gt;.zshrc&amp;lt;/text&amp;gt;
&amp;lt;text x=&quot;48&quot; y=&quot;130&quot;&amp;gt;.zprofile&amp;lt;/text&amp;gt;&amp;lt;text x=&quot;48&quot; y=&quot;150&quot;&amp;gt;.bash_profile&amp;lt;/text&amp;gt;
&amp;lt;text x=&quot;48&quot; y=&quot;170&quot;&amp;gt;.npmrc&amp;lt;/text&amp;gt;&amp;lt;text x=&quot;48&quot; y=&quot;190&quot;&amp;gt;.gitconfig&amp;lt;/text&amp;gt;
&amp;lt;text x=&quot;48&quot; y=&quot;210&quot;&amp;gt;.python_history&amp;lt;/text&amp;gt;&amp;lt;text x=&quot;48&quot; y=&quot;230&quot;&amp;gt;.zsh_history&amp;lt;/text&amp;gt;
&amp;lt;text x=&quot;48&quot; y=&quot;250&quot;&amp;gt;.cargo/  .rustup/  …&amp;lt;/text&amp;gt;
&amp;lt;text x=&quot;48&quot; y=&quot;270&quot; fill-opacity=&quot;0.4&quot;&amp;gt;(増え続ける)&amp;lt;/text&amp;gt;
&amp;lt;/g&amp;gt;
&amp;lt;text x=&quot;330&quot; y=&quot;28&quot; font-size=&quot;13&quot; font-weight=&quot;700&quot; font-family=&quot;ui-sans-serif, system-ui, sans-serif&quot; fill=&quot;currentColor&quot;&amp;gt;After (ZDOTDIR)&amp;lt;/text&amp;gt;
&amp;lt;rect x=&quot;330&quot; y=&quot;40&quot; width=&quot;250&quot; height=&quot;70&quot; rx=&quot;10&quot; fill=&quot;currentColor&quot; fill-opacity=&quot;0.05&quot; stroke=&quot;currentColor&quot; stroke-opacity=&quot;0.15&quot;/&amp;gt;
&amp;lt;text x=&quot;346&quot; y=&quot;66&quot; font-size=&quot;13&quot; fill=&quot;currentColor&quot; fill-opacity=&quot;0.85&quot;&amp;gt;~/&amp;lt;/text&amp;gt;
&amp;lt;text x=&quot;358&quot; y=&quot;90&quot; font-size=&quot;12.5&quot; fill=&quot;var(--primary)&quot;&amp;gt;.zshenv  → ZDOTDIR を設定&amp;lt;/text&amp;gt;
&amp;lt;rect x=&quot;330&quot; y=&quot;125&quot; width=&quot;250&quot; height=&quot;155&quot; rx=&quot;10&quot; fill=&quot;currentColor&quot; fill-opacity=&quot;0.05&quot; stroke=&quot;currentColor&quot; stroke-opacity=&quot;0.15&quot;/&amp;gt;
&amp;lt;text x=&quot;346&quot; y=&quot;151&quot; font-size=&quot;13&quot; fill=&quot;currentColor&quot; fill-opacity=&quot;0.85&quot;&amp;gt;~/.config/zsh/&amp;lt;/text&amp;gt;
&amp;lt;g font-size=&quot;12.5&quot; fill=&quot;currentColor&quot; fill-opacity=&quot;0.6&quot;&amp;gt;
&amp;lt;text x=&quot;358&quot; y=&quot;173&quot;&amp;gt;.zshrc&amp;lt;/text&amp;gt;&amp;lt;text x=&quot;358&quot; y=&quot;193&quot;&amp;gt;00-env.zsh&amp;lt;/text&amp;gt;
&amp;lt;text x=&quot;358&quot; y=&quot;213&quot;&amp;gt;20-aliases.zsh&amp;lt;/text&amp;gt;&amp;lt;text x=&quot;358&quot; y=&quot;233&quot;&amp;gt;30-functions.zsh&amp;lt;/text&amp;gt;
&amp;lt;text x=&quot;358&quot; y=&quot;253&quot;&amp;gt;35-tools.zsh  …&amp;lt;/text&amp;gt;
&amp;lt;/g&amp;gt;
&amp;lt;/g&amp;gt;
&amp;lt;/svg&amp;gt;
&amp;lt;/figure&amp;gt;&lt;/p&gt;
&lt;p&gt;これは zsh が正式にサポートする仕組みだ。VS Code の integrated terminal も &lt;code&gt;~/.zshenv&lt;/code&gt; を読む。古いツールの一部は &lt;code&gt;~/.zshrc&lt;/code&gt; を決め打ちしているが、私の環境では問題になることは少なかった。&lt;/p&gt;
&lt;h2&gt;長い一ファイルを作らない&lt;/h2&gt;
&lt;p&gt;移動先で再び巨大な &lt;code&gt;.zshrc&lt;/code&gt; を作らないよう、&lt;code&gt;.zshrc&lt;/code&gt; 自体は番号順に fragment を読むだけにした。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# ~/.config/zsh/.zshrc — loads every fragment in numeric order
for _file in &quot;${ZDOTDIR}&quot;/conf.d/*.zsh(N); do
  source &quot;$_file&quot;
done
unset _file
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;(N)&lt;/code&gt; は、対象がないときにエラーではなく空へ展開する zsh の glob qualifier。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;~/.config/zsh/conf.d/
├── 00-env.zsh          # exported env vars, PATH base
├── 10-completion.zsh   # compinit and completion styles
├── 20-aliases.zsh      # short renames of existing commands
├── 30-functions.zsh    # shell functions that do real work
├── 35-tools.zsh        # eval-hooks for external CLIs (mise, zoxide, …)
├── 40-lang.zsh         # language/runtime-specific setup
├── 50-plugins.zsh      # zsh-ecosystem plugins
└── 90-local.zsh        # machine-specific, not tracked in git
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;環境変数、関数、外部 CLI が生成する hook、言語設定、プラグインの順に分ける。prompt が runtime 情報を使う場合は &lt;code&gt;mise&lt;/code&gt; の activation より後に読み、syntax highlighting は line editor を wrap するので最後に置く。&lt;/p&gt;
&lt;h2&gt;PATH を重複させない&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;~/.zshenv&lt;/code&gt; は子 shell でも実行されるため、&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;export PATH=&quot;$HOME/.local/bin:$PATH&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;をそのまま置くと tmux、subshell、&lt;code&gt;exec zsh&lt;/code&gt; のたびに同じパスが増える。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# Prepend to PATH only if not already present.
path_prepend() {
  case &quot;:$PATH:&quot; in
    *&quot;:$1:&quot;*) ;;            # already there — do nothing
    *) PATH=&quot;$1:$PATH&quot; ;;
  esac
}

path_prepend &quot;$HOME/.local/bin&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;PATH&lt;/code&gt; の両端にもコロンを付けることで、先頭と末尾の要素も完全一致で確認できる。この関数は &lt;code&gt;00-env.zsh&lt;/code&gt; に置く。&lt;/p&gt;
&lt;h2&gt;chezmoi の source と target&lt;/h2&gt;
&lt;p&gt;Git リポジトリを &lt;code&gt;$HOME&lt;/code&gt; に直接 symlink する代わりに &lt;a href=&quot;https://www.chezmoi.io/&quot;&gt;chezmoi&lt;/a&gt; を使う。source directory からホームディレクトリへレンダリングし、source 側のファイル名で属性も宣言する。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;dot_&lt;/code&gt;：先頭にドットを付ける。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;private_&lt;/code&gt;：結果を &lt;code&gt;0600&lt;/code&gt; にする。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;executable_&lt;/code&gt;：実行 bit を付ける。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;exact_&lt;/code&gt;：宣言されていないディレクトリ内容を削除する。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;run_onchange_&lt;/code&gt;：内容が変わったときにスクリプトを実行する。&lt;a href=&quot;/ja/posts/apps/&quot;&gt;アプリの記事&lt;/a&gt;では Brewfile の変更時に使う。&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;chezmoi source repo            →  rendered into $HOME
├── dot_zshenv                 →  ~/.zshenv
├── dot_config/
│   └── zsh/
│       ├── dot_zshrc          →  ~/.config/zsh/.zshrc
│       └── conf.d/
│           ├── 00-env.zsh     →  ~/.config/zsh/conf.d/00-env.zsh
│           └── 20-aliases.zsh →  ~/.config/zsh/conf.d/20-aliases.zsh
└── dot_local/
    └── bin/
        └── executable_zhealth →  ~/.local/bin/zhealth  (chmod +x)
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;zhealth で勝手に増えた設定を探す&lt;/h2&gt;
&lt;p&gt;構成後、&lt;code&gt;$HOME&lt;/code&gt; にある zsh ファイルは &lt;code&gt;~/.zshenv&lt;/code&gt; だけになる。&lt;code&gt;~/.zshrc&lt;/code&gt; が復活したら、インストーラーが書いた可能性が高い。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# 30-functions.zsh — flag stray zsh files in $HOME
zhealth() {
  local stray=(~/.zshrc(N) ~/.zprofile(N) ~/.zlogin(N) ~/.zshrc.*(N))
  if (( ${#stray} )); then
    print -u2 &quot;zhealth: unexpected zsh files in \$HOME:&quot;
    printf &apos;  %s\n&apos; &quot;${stray[@]}&quot; &amp;gt;&amp;amp;2
    return 1
  fi
  print &quot;zhealth: \$HOME is clean — only ~/.zshenv expected&quot;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;新しいツールを入れたあとに実行すれば、数か月後にシェルの挙動から原因を推測せずに済む。&lt;a href=&quot;/ja/posts/maintenance/&quot;&gt;メンテナンスの記事&lt;/a&gt;でも同じ health check の考え方を使う。&lt;/p&gt;
&lt;h2&gt;追跡しないもの&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;~/.zsh_history&lt;/code&gt; は設定ではなく状態で、内容も private。&lt;code&gt;.zcompdump*&lt;/code&gt; は再生成できる cache。credential や token も通常の dotfiles リポジトリには置かない。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# .chezmoiignore
.config/zsh/.zcompdump*
.zsh_history
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;マシン固有の proxy や local alias は &lt;code&gt;90-local.zsh&lt;/code&gt; に置く。最後に読み込むので上書きできるが、Git では管理しない。&lt;/p&gt;
&lt;p&gt;結果として &lt;code&gt;$HOME&lt;/code&gt; には入口が一つ、設定本体は順序付きの小さな fragment、期待状態は chezmoi、勝手に増えたファイルの確認は &lt;code&gt;zhealth&lt;/code&gt; という形になった。&lt;/p&gt;
</content:encoded></item><item><title>Macの開発環境を四つの層で作り直した</title><link>https://www.shiinayane.com/ja/posts/manifesto/</link><guid isPermaLink="true">https://www.shiinayane.com/ja/posts/manifesto/</guid><description>DFUからの再セットアップを機に、開発環境の各要素をどのツールに任せ、どう復元するかを整理した。</description><pubDate>Sat, 30 May 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;数週間前、Mac miniをDFUモードに入れて、すべて消去した。DFU（Device Firmware Update）は、そのMac自身には動作するOSがなく、別のMacからシステムを書き込まれるのを待っている状態だ。&lt;/p&gt;
&lt;p&gt;きっかけは、なかなか直らない問題だった。ただ、単純にもう一度きれいな環境を作りたかったのも事実だ。ソフトウェアを入れ直すだけの退屈な午後になると思っていたが、その前に「きれいな開発環境とは何か」を決める必要があった。&lt;/p&gt;
&lt;p&gt;以前は、インストールするツールを減らせばよいと思っていた。しかし、そのような最小構成は半年もすると崩れた。内容を思い出せない&lt;code&gt;~/.zshrc&lt;/code&gt;、自分で選んだ覚えのない三つのPython、置かれるべきでない場所に残った2023年の&lt;code&gt;pip install&lt;/code&gt;。ツールの数よりも、何をどれが管理しているのか分からないことのほうが問題だった。&lt;/p&gt;
&lt;p&gt;この記事は全7本のシリーズの1本目にあたる。残りの6本で個別のツールを扱う前に、再セットアップで採用した四層構造をここにまとめておく。&lt;/p&gt;
&lt;h2&gt;普通のインストールを重ねた結果&lt;/h2&gt;
&lt;p&gt;環境は、一度の極端な操作で壊れるとは限らない。&lt;/p&gt;
&lt;p&gt;Pythonが必要になり、手近なHomebrewで&lt;code&gt;brew install python&lt;/code&gt;を実行する。別のバージョンが必要なプロジェクトのために&lt;code&gt;pyenv&lt;/code&gt;を追加し、Nodeの案件で&lt;code&gt;nvm&lt;/code&gt;、Rubyで&lt;code&gt;rbenv&lt;/code&gt;も入れる。それぞれのインストーラーが&lt;code&gt;~/.zshrc&lt;/code&gt;に数行ずつ追記するが、気付かないことも多い。さらに&lt;code&gt;pip install --user&lt;/code&gt;を使うと、別のツールが想定していない場所のパッケージが先に読まれることがある。&lt;/p&gt;
&lt;p&gt;一つずつ見れば、どれも不自然な判断ではない。困るのは、&lt;code&gt;python&lt;/code&gt;と入力したときに、どのPythonがなぜ実行されるのかを自分のMacで調査しなければならなくなったときだ。&lt;/p&gt;
&lt;p&gt;その答えが、現在の&lt;code&gt;PATH&lt;/code&gt;の偶然ではなく、構成から決まる状態にしたかった。&lt;/p&gt;
&lt;h2&gt;四つの層&lt;/h2&gt;
&lt;p&gt;Mac miniの環境を、システムからリポジトリ内の依存関係まで四つに分けた。&lt;/p&gt;
&lt;p&gt;&amp;lt;figure class=&quot;my-6&quot;&amp;gt;
&amp;lt;svg viewBox=&quot;0 0 600 330&quot; role=&quot;img&quot; aria-labelledby=&quot;diagram-layers-title&quot; style=&quot;width:100%;height:auto;color:inherit&quot;&amp;gt;
&amp;lt;title id=&quot;diagram-layers-title&quot;&amp;gt;The four-layer stack: System, Runtime version, Package manager, Project dependencies&amp;lt;/title&amp;gt;
&amp;lt;g font-family=&quot;ui-sans-serif, system-ui, sans-serif&quot;&amp;gt;
&amp;lt;!-- Layer 3 --&amp;gt;
&amp;lt;rect x=&quot;10&quot; y=&quot;10&quot; width=&quot;580&quot; height=&quot;66&quot; rx=&quot;10&quot; fill=&quot;currentColor&quot; fill-opacity=&quot;0.05&quot; stroke=&quot;currentColor&quot; stroke-opacity=&quot;0.15&quot;/&amp;gt;
&amp;lt;text x=&quot;30&quot; y=&quot;38&quot; font-size=&quot;13&quot; font-weight=&quot;700&quot; fill=&quot;var(--primary)&quot;&amp;gt;Layer 3&amp;lt;/text&amp;gt;
&amp;lt;text x=&quot;30&quot; y=&quot;58&quot; font-size=&quot;15&quot; font-weight=&quot;600&quot; fill=&quot;currentColor&quot;&amp;gt;Project dependencies&amp;lt;/text&amp;gt;
&amp;lt;text x=&quot;570&quot; y=&quot;44&quot; font-size=&quot;13&quot; text-anchor=&quot;end&quot; fill=&quot;currentColor&quot; fill-opacity=&quot;0.6&quot;&amp;gt;pyproject.toml + uv.lock, package.json + pnpm-lock.yaml&amp;lt;/text&amp;gt;
&amp;lt;!-- Layer 2 --&amp;gt;
&amp;lt;rect x=&quot;10&quot; y=&quot;86&quot; width=&quot;580&quot; height=&quot;66&quot; rx=&quot;10&quot; fill=&quot;currentColor&quot; fill-opacity=&quot;0.05&quot; stroke=&quot;currentColor&quot; stroke-opacity=&quot;0.15&quot;/&amp;gt;
&amp;lt;text x=&quot;30&quot; y=&quot;114&quot; font-size=&quot;13&quot; font-weight=&quot;700&quot; fill=&quot;var(--primary)&quot;&amp;gt;Layer 2&amp;lt;/text&amp;gt;
&amp;lt;text x=&quot;30&quot; y=&quot;134&quot; font-size=&quot;15&quot; font-weight=&quot;600&quot; fill=&quot;currentColor&quot;&amp;gt;Package manager&amp;lt;/text&amp;gt;
&amp;lt;text x=&quot;570&quot; y=&quot;120&quot; font-size=&quot;13&quot; text-anchor=&quot;end&quot; fill=&quot;currentColor&quot; fill-opacity=&quot;0.6&quot;&amp;gt;uv, pnpm, cargo&amp;lt;/text&amp;gt;
&amp;lt;!-- Layer 1 --&amp;gt;
&amp;lt;rect x=&quot;10&quot; y=&quot;162&quot; width=&quot;580&quot; height=&quot;66&quot; rx=&quot;10&quot; fill=&quot;currentColor&quot; fill-opacity=&quot;0.05&quot; stroke=&quot;currentColor&quot; stroke-opacity=&quot;0.15&quot;/&amp;gt;
&amp;lt;text x=&quot;30&quot; y=&quot;190&quot; font-size=&quot;13&quot; font-weight=&quot;700&quot; fill=&quot;var(--primary)&quot;&amp;gt;Layer 1&amp;lt;/text&amp;gt;
&amp;lt;text x=&quot;30&quot; y=&quot;210&quot; font-size=&quot;15&quot; font-weight=&quot;600&quot; fill=&quot;currentColor&quot;&amp;gt;Runtime version&amp;lt;/text&amp;gt;
&amp;lt;text x=&quot;570&quot; y=&quot;196&quot; font-size=&quot;13&quot; text-anchor=&quot;end&quot; fill=&quot;currentColor&quot; fill-opacity=&quot;0.6&quot;&amp;gt;mise (or a language&apos;s sovereign tool)&amp;lt;/text&amp;gt;
&amp;lt;!-- Layer 0 --&amp;gt;
&amp;lt;rect x=&quot;10&quot; y=&quot;238&quot; width=&quot;580&quot; height=&quot;66&quot; rx=&quot;10&quot; fill=&quot;currentColor&quot; fill-opacity=&quot;0.08&quot; stroke=&quot;currentColor&quot; stroke-opacity=&quot;0.2&quot;/&amp;gt;
&amp;lt;text x=&quot;30&quot; y=&quot;266&quot; font-size=&quot;13&quot; font-weight=&quot;700&quot; fill=&quot;var(--primary)&quot;&amp;gt;Layer 0&amp;lt;/text&amp;gt;
&amp;lt;text x=&quot;30&quot; y=&quot;286&quot; font-size=&quot;15&quot; font-weight=&quot;600&quot; fill=&quot;currentColor&quot;&amp;gt;System&amp;lt;/text&amp;gt;
&amp;lt;text x=&quot;570&quot; y=&quot;272&quot; font-size=&quot;13&quot; text-anchor=&quot;end&quot; fill=&quot;currentColor&quot; fill-opacity=&quot;0.6&quot;&amp;gt;Homebrew + Xcode Command Line Tools&amp;lt;/text&amp;gt;
&amp;lt;/g&amp;gt;
&amp;lt;/svg&amp;gt;
&amp;lt;/figure&amp;gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Layer 0 — System.&lt;/strong&gt; HomebrewとXcode Command Line Toolsが、コマンドラインツールとGUIアプリケーションをインストールする。言語のランタイムは入れない。HomebrewにPythonまで管理させた時点で、Layer 0が次の層に入り込む。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Layer 1 — Runtime version.&lt;/strong&gt; Python 3.14やNode 22など、使用するインタープリターやコンパイラーのバージョンを選ぶ層。通常は&lt;code&gt;mise&lt;/code&gt;に任せ、現在いるプロジェクトのディレクトリに応じてランタイムを切り替える。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Layer 2 — Package manager.&lt;/strong&gt; Pythonでは&lt;code&gt;uv&lt;/code&gt;、Nodeでは&lt;code&gt;pnpm&lt;/code&gt;、Rustでは&lt;code&gt;cargo&lt;/code&gt;を使う。選ばれたランタイムの中にパッケージをインストールするツールであり、ランタイムのバージョンは選ばない。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Layer 3 — Project dependencies.&lt;/strong&gt; ManifestとLockfileはリポジトリに置く。たとえば&lt;code&gt;pyproject.toml&lt;/code&gt;と&lt;code&gt;uv.lock&lt;/code&gt;、または&lt;code&gt;package.json&lt;/code&gt;と&lt;code&gt;pnpm-lock.yaml&lt;/code&gt;である。別のマシンでも、これらのファイルから同じ依存関係を再現できるようにする。&lt;/p&gt;
&lt;p&gt;依存する方向も重要になる。プロジェクトの依存関係はPackage Managerを使い、Package ManagerはRuntimeに依存し、RuntimeはSystemの上で動く。各ツールは自分の層を担当し、上下の層まで暗黙に管理しない。&lt;/p&gt;
&lt;h2&gt;一種類につき一つの管理元&lt;/h2&gt;
&lt;p&gt;実際に守っているのは、同じ種類のものを複数のツールに管理させない、というルールだ。&lt;/p&gt;
&lt;p&gt;Runtime versionの管理元は一つ、プロジェクトのLibraryも一つ、System applicationも一つにする。Pythonがどこから来たか、依存バージョンを何が固定しているか、なぜそのアプリが入っているかを知りたいとき、確認する場所が一つに決まる。&lt;/p&gt;
&lt;p&gt;そのため、ツールの総数を減らしてもあまり意味はなかった。二つのツールがどちらもPythonを管理しようとする環境より、五つのツールが別々の役割を持つ環境のほうが追いやすい。&lt;/p&gt;
&lt;p&gt;Layer 1の管理元は、常に&lt;code&gt;mise&lt;/code&gt;というわけでもない。Rustには&lt;code&gt;rustup&lt;/code&gt;があり、SwiftにはXcodeがある。どちらも言語のエコシステム自身が提供する、十分に強い公式のToolchain Managerだ。このようなツールがある場合は、&lt;code&gt;mise&lt;/code&gt;を前に置かず、RuntimeとToolchainを公式ツールに直接任せる。この判断方法はシリーズの5本目で扱う。&lt;/p&gt;
&lt;p&gt;つまりSingle Source of Truthは特定の製品名ではなく、管理元が重ならない状態を指している。ツールが入れ替わっても、この分担は残せる。&lt;/p&gt;
&lt;h2&gt;復元できるかで確かめる&lt;/h2&gt;
&lt;p&gt;消去を経験してからは、復元可能かどうかを環境のチェックに使っている。&lt;/p&gt;
&lt;p&gt;キャッシュとビルド生成物をすべて削除して、つまりまとめて&lt;code&gt;rm -rf&lt;/code&gt;しても、マシン上のどのプロジェクトも一つのコマンドで再ビルドできる状態が理想だ。それができれば、キャッシュは本当に捨てられる。できなければ、「生成物」と思っていた場所が、宣言されていない状態を抱えている。どのSource of Truthが欠けているのかを探し、層の分担を直す。&lt;/p&gt;
&lt;p&gt;保存すべき宣言はManifest、Lockfile、Dotfilesにある。ほぼ何もない状態からでも、それらを使って実際に動く環境を戻せるようにする。普段の調査でも、いきなり&lt;code&gt;PATH&lt;/code&gt;を追う前に、どのツールを確認すべきか分かる。&lt;/p&gt;
&lt;p&gt;この構成には前提がある。一人のユーザーが全体を管理できるmacOS向けの環境であり、チーム、共有サーバー、制限の多い会社支給Mac、ほかのOSでは判断が大きく変わり得る。&lt;code&gt;mise&lt;/code&gt;と&lt;code&gt;uv&lt;/code&gt;も比較的新しいため、将来は別のツールに置き換わるかもしれない。どこでも通用する規則ではなく、私がこの構成を使っている条件である。&lt;/p&gt;
&lt;h2&gt;続く六つの記事&lt;/h2&gt;
&lt;p&gt;残りの記事では、環境の各部分を順に扱う。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;.zshrc&lt;/code&gt;に詰め込まないDotfiles&lt;/strong&gt;：&lt;code&gt;ZDOTDIR&lt;/code&gt;と&lt;code&gt;chezmoi&lt;/code&gt;でShell設定を宣言的に管理し、差分を見えるようにして、Home directoryを設定置き場にしない。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Brewfileという妥協&lt;/strong&gt;：System layerではEventual Consistencyを許容する理由と、厳密であるかのように装わずReconcileで状態を保つ方法。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Python、mise、uv&lt;/strong&gt;：&lt;code&gt;uv&lt;/code&gt;が&lt;code&gt;mise&lt;/code&gt;の裏で独自のPythonをインストールする衝突と、それを解消する一つの設定。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;公式ツールを優先するとき&lt;/strong&gt;：Layer 1を&lt;code&gt;mise&lt;/code&gt;ではなく、言語自身のVersion Managerに任せるための判断基準。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;一つの構造を複数の言語へ&lt;/strong&gt;：同じ四層をNode、Java、Swift、Rust、Go、Rubyに当てはめる。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;設定よりも維持が難しい&lt;/strong&gt;：Health Check、不要なものを減らす習慣、長く使ったあとも環境を理解できる状態に保つ作業。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;a href=&quot;/ja/series/sovereign-tools/&quot;&gt;シリーズ一覧&lt;/a&gt;に、全記事の順番と現在の公開状況を載せている。&lt;/p&gt;
</content:encoded></item><item><title>Swift の関数シグネチャを読めるようになるまで</title><link>https://www.shiinayane.com/ja/posts/reading-swift-function-signatures/</link><guid isPermaLink="true">https://www.shiinayane.com/ja/posts/reading-swift-function-signatures/</guid><pubDate>Thu, 07 May 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Swift を学び始めた頃、Apple の DocC を開いてこんな記述が出てくると、そのまま使用例までスクロールしていた。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;func compactMap&amp;lt;ElementOfResult&amp;gt;(
    _ transform: (Self.Element) throws -&amp;gt; ElementOfResult?
) rethrows -&amp;gt; [ElementOfResult]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;率直に言えば、「一つの関数が、どうしてこんなに怖い見た目になるのか」と思っていた。&lt;/p&gt;
&lt;p&gt;それまで慣れていた Python の API は、もっと素直に見えた。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;map(func, arr)
filter(func, arr)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Swift はジェネリクス、&lt;code&gt;Optional&lt;/code&gt;、関連型、プロトコル制約、&lt;code&gt;throws&lt;/code&gt;、&lt;code&gt;rethrows&lt;/code&gt; を一行に詰め込んでくる。私はそれらを読むべき情報ではなく、使用例へたどり着くまでのノイズとして扱っていた。サンプルを真似するだけならそれでもよかったが、知らない API は自力で読めないままだった。&lt;/p&gt;
&lt;h2&gt;まず短いシグネチャで考える&lt;/h2&gt;
&lt;p&gt;長いジェネリック関数より先に、次のような短いものを見ると、型が何を伝えているのかわかりやすい。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;func popLast() -&amp;gt; Element?
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;戻り値が Optional なので、コレクションが空なら &lt;code&gt;nil&lt;/code&gt; になる。「最後の要素がない」という状態をどう扱うのかが、一行の中に書かれている。&lt;/p&gt;
&lt;p&gt;そこで、次の二つを比べてみる。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;removeLast()
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;そして：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;popLast()
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;removeLast()&lt;/code&gt; はコレクションが空でないことを前提とし、空なら実行時エラーになる。&lt;code&gt;popLast()&lt;/code&gt; は空の場合を &lt;code&gt;nil&lt;/code&gt; で扱う。名前も違いを示しているが、Optional の戻り値を見れば、呼び出し側が処理すべき条件までわかる。&lt;/p&gt;
&lt;h2&gt;長い場合は値の流れを追う&lt;/h2&gt;
&lt;p&gt;この読み方をジェネリック関数に広げるきっかけになったのが &lt;code&gt;map&lt;/code&gt; だった。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;func map&amp;lt;T&amp;gt;(
    _ transform: (Element) throws -&amp;gt; T
) rethrows -&amp;gt; [T]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;記号を端から全部理解しようとせず、クロージャの矢印から追う。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;一つの &lt;code&gt;Element&lt;/code&gt; がクロージャに入る&lt;/li&gt;
&lt;li&gt;そこから何らかの型 &lt;code&gt;T&lt;/code&gt; が返る&lt;/li&gt;
&lt;li&gt;&lt;code&gt;map&lt;/code&gt; は結果を &lt;code&gt;[T]&lt;/code&gt; にまとめる&lt;/li&gt;
&lt;li&gt;クロージャはエラーを投げてもよい&lt;/li&gt;
&lt;li&gt;&lt;code&gt;rethrows&lt;/code&gt; なので、そのクロージャが投げた場合に限って &lt;code&gt;map&lt;/code&gt; もエラーを投げる&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;code&gt;T&lt;/code&gt; の実体は呼び出し方によって変わる。それでも、入力と出力が同じ型である必要はない、とシグネチャだけで判断できる。&lt;/p&gt;
&lt;p&gt;よく出てくる部品も、実際にはそれほど多くない。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;(Element) -&amp;gt; T
(Element) -&amp;gt; T?
Sequence&amp;lt;Element&amp;gt;
where T : BinaryInteger
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;上から順に、&lt;code&gt;Element&lt;/code&gt; を &lt;code&gt;T&lt;/code&gt; に変える関数、結果がない場合もある関数、要素型が &lt;code&gt;Element&lt;/code&gt; のシーケンス、&lt;code&gt;BinaryInteger&lt;/code&gt; への準拠を要求される型 &lt;code&gt;T&lt;/code&gt; と読める。一つの使用例を暗記するより、この関係を読めるほうが標準ライブラリでもサードパーティ製 API でも使い回しが利く。&lt;/p&gt;
&lt;h2&gt;三つの変換メソッドを型で区別する&lt;/h2&gt;
&lt;p&gt;以前の私は、&lt;code&gt;map&lt;/code&gt; を次の形で使うことしか覚えていなかった。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;arr.map { ... }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;map&lt;/code&gt;、&lt;code&gt;compactMap&lt;/code&gt;、&lt;code&gt;flatMap&lt;/code&gt; の違いが腑に落ちたのは、クロージャとメソッドの戻り値を見比べてからだった。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;compactMap&lt;/code&gt; では、一つの &lt;code&gt;?&lt;/code&gt; が動作を決めている。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;func compactMap&amp;lt;ElementOfResult&amp;gt;(
    _ transform: (Element) throws -&amp;gt; ElementOfResult?
) rethrows -&amp;gt; [ElementOfResult]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;変換クロージャは &lt;code&gt;ElementOfResult&lt;/code&gt; または &lt;code&gt;nil&lt;/code&gt; を返せる。一方、最終的な配列の要素は Optional ではない。つまり各入力を変換し、&lt;code&gt;nil&lt;/code&gt; になった結果を除外する。「Optional のときに使うもの」という覚え方より、こちらのほうが正確だった。&lt;/p&gt;
&lt;p&gt;シーケンス向けの &lt;code&gt;flatMap&lt;/code&gt; オーバーロードはさらに長い。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;func flatMap&amp;lt;SegmentOfResult&amp;gt;(
    _ transform: (Element) throws -&amp;gt; SegmentOfResult
) rethrows -&amp;gt; [SegmentOfResult.Element]
where SegmentOfResult : Sequence
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;where&lt;/code&gt; 節により、変換後の &lt;code&gt;SegmentOfResult&lt;/code&gt; 自体が &lt;code&gt;Sequence&lt;/code&gt; に制約されている。戻り値はシーケンスの配列ではなく、その内側の要素を集めた配列だ。&lt;/p&gt;
&lt;p&gt;そのため、次のような入れ子の値を：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;[[1], [2,2], [3,3,3]]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;次の形にできる：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;[1,2,2,3,3,3]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;この型の関係がわかると、&lt;code&gt;flatMap&lt;/code&gt; を「高度な map」のように考えずに済む。このオーバーロードがしているのは、要素をシーケンスへ変換し、それらを一つの配列へ平坦化することだ。&lt;/p&gt;
&lt;h2&gt;コンパイル時に引き受けるもの&lt;/h2&gt;
&lt;p&gt;Swift はしばしば：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;more complexity during compilation
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;を受け入れる代わりに、実行時の：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;more uncertainty at runtime
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;を減らそうとする。完全な交換条件ではないが、その一部はコンパイラで早めに検出したり、型として明示したりできる。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;Optional&lt;/code&gt;、ジェネリクス、プロトコル指向の API、型制約、明示的なエラー処理は、どれもシグネチャを長くする要因であり、最初に私が読むのを嫌がった部分でもある。これだけですべての不正な状態を防げるわけではなく、シグネチャだけで文書が不要になるわけでもない。それでも、入力、出力、失敗の扱い、型同士の関係は、コードを実行する前からかなり読み取れる。&lt;/p&gt;
&lt;p&gt;知らない型に出会えば、今でも使用例は開く。ただし、まずシグネチャから動作を予想し、その理解が合っているかを例で確かめるようになった。&lt;/p&gt;
</content:encoded></item><item><title>Raptorでぶつかったジレンマ</title><link>https://www.shiinayane.com/ja/posts/the-raptor-dilemma/</link><guid isPermaLink="true">https://www.shiinayane.com/ja/posts/the-raptor-dilemma/</guid><description>SwiftUIのような理想とWebの現実</description><pubDate>Sat, 02 May 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;&lt;a href=&quot;/ja/posts/swift-for-static-sites/&quot;&gt;Swiftで静的サイトを作る&lt;/a&gt;では、Astro、Saga、Toucan、Publish、Ignite、Raptorを比較した。その時点での結論は、Swiftはコンテンツのモデル化と生成処理を担当し、UIはHTML、CSS、JavaScriptに任せるのがよさそう、というものだった。&lt;/p&gt;
&lt;p&gt;ただし、その判断にはドキュメントやサンプルを読んだ印象も含まれていた。そのあとRaptorで実際にテーマを一つ作ってみると、引っかかっていた点がもっとはっきりした。&lt;/p&gt;
&lt;p&gt;ページが単純なうちは、とても書きやすい。ところが独自のブログテーマを細部まで再現し始めると、Swiftでコンポーネントを書き、CSSで考え、最後はブラウザでデバッグすることになった。特定のバグではなく、この距離そのものが問題だった。&lt;/p&gt;
&lt;h2&gt;最初はうまくいった&lt;/h2&gt;
&lt;p&gt;Raptorは単にMarkdownからHTMLを生成するだけではない。ページ、レイアウト、テーマ、スタイル、コンポーネントをSwift中心で組み立てるモデルを提供している。通常なら次のように書く部分を、&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;article class=&quot;post-card&quot;&amp;gt;
  &amp;lt;h2&amp;gt;Title&amp;lt;/h2&amp;gt;
&amp;lt;/article&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Raptorではこう書ける。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;VStack {
    Text(post.title)
}
.style(PostCardStyle())
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;型安全で、部品を組み合わせやすく、実装言語をSwiftに揃えられる。開発体験もSwiftUIに近い。SwiftUIがAppleプラットフォームにもたらした書きやすさを、Webにも持ち込めそうに見える。&lt;/p&gt;
&lt;p&gt;単純なページなら、その期待どおりに動く。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Text(&quot;Hello&quot;)
Button(&quot;Read more&quot;)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;ランディングページ、ドキュメントサイト、基本的なブログは素早く作れる。UIの大半が次の要素に収まるなら、&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Text + Button + Grid + Card
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Raptorの抽象化は簡潔で使いやすい。&lt;/p&gt;
&lt;h2&gt;独自テーマで境界が見えた&lt;/h2&gt;
&lt;p&gt;困ったのは特殊なブラウザ機能ではなく、ブログテーマではよくある細部だった。&lt;/p&gt;
&lt;h3&gt;メタ情報の横並び&lt;/h3&gt;
&lt;p&gt;カテゴリーを左、時刻を右に置くレイアウトは、CSSならそのまま表現できる。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;.meta {
  display: flex;
  justify-content: space-between;
}
.meta time {
  float: right;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Raptorでは、同じ意図をコンポーネントの配置に置き換える。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;HStack {
    categories
    Spacer().axis(.horizontal)
    time
}
.style(Property.width(.percent(100)))
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;これは問題ない。むしろ見通しがよく、抽象化と目的がきちんと一致している。&lt;/p&gt;
&lt;h3&gt;装飾用の疑似要素&lt;/h3&gt;
&lt;p&gt;次は見出しの下に置く短いアクセントバーだった。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;.recent-info::after {
  content: &apos;&apos;;
  width: 13%;
  height: 5px;
  background: var(--accent);
  position: relative;
  bottom: -6px;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Raptorでは実体のあるコンポーネントにした。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;RecentInfoAccentBar()
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;表示結果は作れるが、意味は変わる。CSSでは既存要素に付随する装飾レイヤーだったものが、コンポーネントツリーでは構造の一部になる。スタイルを再現するために、文書の構造を組み替えることになった。&lt;/p&gt;
&lt;h3&gt;要素をまたぐHover&lt;/h3&gt;
&lt;p&gt;CSSセレクターは、二つの要素の関係も直接表現できる。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;.card:hover .read-more {
  background-color: var(--bg-hover);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Raptorのコンポーネントモデルには、これと同じくらい素直な書き方がなかった。インタラクションを設計し直すか、関係するスタイルを手作業で連携させる必要がある。「親がHoverされたら子孫の見た目を変える」という関係はブラウザなら最初から理解できるのに、Swiftの抽象化を通すと表しにくくなる。&lt;/p&gt;
&lt;h3&gt;負のマージン&lt;/h3&gt;
&lt;p&gt;よくある細かな位置調整も、結局はCSSの操作だ。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;.read-more {
  margin-top: -21px;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Raptorでも同じ値は指定できる。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;.style(Property.marginTop(.px(-21)))
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;ここまで来ると、CSSから離れたのではなく、CSSをSwiftの構文に訳しているだけだった。&lt;/p&gt;
&lt;p&gt;このずれを短くまとめると、次のようになる。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;They think in Swift,
but debug in CSS.

They write components,
but fight layout at the DOM level.

They define styles,
but still rely on raw CSS properties.
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;一つのテーマに二つの考え方&lt;/h2&gt;
&lt;p&gt;カードの構造自体は、Swiftのコンポーネントとしてきれいに書ける。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;PostListItem {
    PostMeta(...)
    PostTitle(...)
    PostExcerpt(...)
    PostReadMore(...)
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;共通のスタイルもまとめられる。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;.style(PostCardStyle())
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;しかし見た目を詰めていくと、指定は少しずつ増えていった。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;.style(Property.marginTop(.px(12)))
.style(Property.paddingLeft(.px(8)))
.style(Property.fontSize(.px(14)))
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;生成後のページが動くのはHTML、CSS、JavaScriptの上なので、最終的な正解はブラウザの挙動で決まる。実際の作業は次のように分かれた。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Structure → Swift
Styling → CSS concepts
Layout debugging → Browser DevTools
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;感覚としてはこうなる。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Half SwiftUI
Half traditional Web
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;複雑さがなくなったわけではなく、場所が移っただけだ。Swiftでコンポーネントを書き、生成されたDOMを確認し、CSSの知識でレイアウトを直していた。&lt;/p&gt;
&lt;h2&gt;Sagaは境界を隠さない&lt;/h2&gt;
&lt;p&gt;Sagaは別の方向を選んでいる。Swiftで構造を組み立てながら、生成されるHTMLとclassもコードからすぐ読み取れる。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;article(class: &quot;mx-auto max-w-3xl px-6 py-12&quot;) {
  h1(class: &quot;text-4xl font-bold tracking-tight&quot;) {
    item.title
  }
  div(class: &quot;prose&quot;) {
    raw(item.body)
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;役割分担はそのまま見えている。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Swift → structure + composition
HTML → structure
CSS → styling
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;書いたコードとブラウザで調べるページの間にある翻訳が少ない。見た目を細かく作り込むサイトでは、Webの上にSwiftUI風のレイヤーを置くより、こちらのほうが理解しやすかった。&lt;/p&gt;
&lt;h2&gt;Raptorを使う範囲&lt;/h2&gt;
&lt;p&gt;SwiftでWeb UIを表現すること自体はできる。考えるべきなのは、レンダリング環境からどこまで離れると抽象化の利点が消えるかだ。&lt;/p&gt;
&lt;p&gt;汎用的なUIでは、&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Abstraction helps
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;高度にカスタマイズしたUIでは、&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Abstraction fights the platform
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Raptorが強いのは前者だ。一般的なコンポーネントで構成されたページなら、型安全と合成のしやすさがそのまま利点になる。後者では、セレクター、疑似要素、小さなレイアウト調整が重要になるほど、抽象化が扱いにくくなる。&lt;/p&gt;
&lt;p&gt;スタック全体には、やはり二つの層がある。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Rendering layer → HTML / CSS / JS
Authoring layer → Swift / React / Astro
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;ReactやAstroはレンダリング層との距離が比較的近い。Raptorは意図的にそこから離れることでSwiftUIらしい体験を作っているが、その距離が今回のテーマ制作ではずれとして現れた。&lt;/p&gt;
&lt;p&gt;このテーマを作ったあと、自分の中では次の境界に落ち着いた。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Swift for logic → great
Swift for UI abstraction → situational
HTML/CSS/JS → still the source of truth
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;構造が単純で、汎用コンポーネントが中心のサイトなら、今でもRaptorを選べる。視覚的な細部が多いテーマなら、Sagaのような方法でブラウザ本来のUIモデルを見えるままにしておきたい。&lt;/p&gt;
</content:encoded></item><item><title>Swiftで静的サイトを作る</title><link>https://www.shiinayane.com/ja/posts/swift-for-static-sites/</link><guid isPermaLink="true">https://www.shiinayane.com/ja/posts/swift-for-static-sites/</guid><description>SwiftUI風の抽象化とWebネイティブな実装の間で分かったこと</description><pubDate>Fri, 01 May 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;複雑な見た目のブログテーマをSwift製の静的サイトジェネレーターへ移すなら、どれを選ぶべきか。&lt;a href=&quot;https://github.com/loopwerk/Saga&quot;&gt;Saga&lt;/a&gt;、&lt;a href=&quot;https://github.com/toucansites/toucan&quot;&gt;Toucan&lt;/a&gt;、&lt;a href=&quot;https://github.com/JohnSundell/Publish&quot;&gt;Publish&lt;/a&gt;、&lt;a href=&quot;https://github.com/twostraws/Ignite&quot;&gt;Ignite&lt;/a&gt;、&lt;a href=&quot;https://github.com/raptor-build/raptor&quot;&gt;Raptor&lt;/a&gt;を一通り調べ、&lt;a href=&quot;https://github.com/withastro/astro&quot;&gt;Astro&lt;/a&gt;、&lt;a href=&quot;https://github.com/hexojs/hexo&quot;&gt;Hexo&lt;/a&gt;、&lt;a href=&quot;https://github.com/gohugoio/hugo&quot;&gt;Hugo&lt;/a&gt;、&lt;a href=&quot;https://github.com/jekyll/jekyll&quot;&gt;Jekyll&lt;/a&gt;のようなWebネイティブのツールとも比べてみた。&lt;/p&gt;
&lt;p&gt;ソースコードやサンプルを読み、実際に触って分かったのは、機能の数より「SwiftをWebスタックのどこに置くか」のほうが重要だということだった。小さなページでは、SwiftUI風のコンポーネントAPIも、Swiftから普通のHTMLを生成する方法も似て見える。それが独自CSSを多用するテーマになると、両者の違いがはっきり出る。&lt;/p&gt;
&lt;h2&gt;UIのランタイムはブラウザ&lt;/h2&gt;
&lt;p&gt;SwiftUIがiOSで自然に使えるのは、その抽象化がシステムのUIランタイムへ直接つながっているからだ。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;iOS:
System UI runtime
→ SwiftUI Button / VStack / NavigationStack
→ Your Swift code
Web:
Browser runtime (DOM + CSS + JS)
→ HTML &amp;lt;button&amp;gt; / &amp;lt;div&amp;gt; / &amp;lt;article&amp;gt;
→ CSS grid / flex / selectors
→ Your HTML/CSS/JS
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;SwiftUIの&lt;code&gt;Button&lt;/code&gt;は、アクセシビリティ、フォーカス、アニメーション、入力といったプラットフォームの挙動に結びついている。一方、RaptorやIgniteで宣言したボタンが最終的に生成するものは、&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;button class=&quot;...&quot;&amp;gt;Save&amp;lt;/button&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;とCSSだ。ブラウザは元のSwiftコンポーネントを知らず、受け取ったHTML、CSS、JavaScriptだけを実行する。&lt;/p&gt;
&lt;p&gt;宣言的UIが問題なのではない。React、Vue、Astroも宣言的であり、違うのは何を宣言しているか、そしてブラウザ本来のモデルからどれだけ離れているかだ。&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;フレームワーク&lt;/th&gt;
&lt;th&gt;宣言するもの&lt;/th&gt;
&lt;th&gt;プラットフォームとの距離&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;SwiftUI&lt;/td&gt;
&lt;td&gt;ネイティブUIツリー&lt;/td&gt;
&lt;td&gt;とても近い&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;React / Vue&lt;/td&gt;
&lt;td&gt;DOM / コンポーネントツリー&lt;/td&gt;
&lt;td&gt;とても近い&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Astro&lt;/td&gt;
&lt;td&gt;HTML + Islands&lt;/td&gt;
&lt;td&gt;とても近い&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Saga / Publish&lt;/td&gt;
&lt;td&gt;HTML出力ツリー&lt;/td&gt;
&lt;td&gt;近い&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Ignite&lt;/td&gt;
&lt;td&gt;Swiftコンポーネント（Bootstrap風）&lt;/td&gt;
&lt;td&gt;中程度&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Raptor&lt;/td&gt;
&lt;td&gt;SwiftUI風UI + 独自スタイルシステム&lt;/td&gt;
&lt;td&gt;より遠い&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;SwiftUIはプラットフォームのモデルとよく一致する。ReactはDOMを扱い、AstroはHTMLをコンポーネントの第一級要素として扱う。IgniteとRaptorは、そこからさらに上にSwiftのコンポーネントツリーを置く。簡単なページでは便利だが、デザインの独自性が高くなるほど変換コストが表に出てくる。&lt;/p&gt;
&lt;h2&gt;コンポーネントDSLが楽ではなくなる地点&lt;/h2&gt;
&lt;p&gt;IgniteのようなAPIは、単純なUIなら素早く書ける。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Text(&quot;Hello&quot;)
Button(&quot;Read More&quot;)
Grid {
  Card { ... }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Bootstrapによってレイアウト、余白、レスポンシブ対応、基本的な視覚階層が最初から用意される。ドキュメント、ポートフォリオ、一般的なブログなど、組み込みの語彙で表現できるサイトにはよく合う。&lt;/p&gt;
&lt;p&gt;ところが、既存のビジュアルテーマを移植しようとすると、次のようなCSSが必要になる。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;.card::before
.sidebar:has(.active)
grid-template-columns: minmax(0, 1fr) 18rem
position: sticky
backdrop-filter
mask-image
container queries
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;組み込みコンポーネントで表現できなくなると、Swift側も結局は低レベルなHTMLラッパーへ戻る。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Tag(&quot;aside&quot;) { ... }
.class(&quot;layout-shell__sidebar&quot;)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;CSSは別途書かなければならない。ページの半分がSwiftUI風の語彙、残りがHTMLのラッパーになり、抽象化によって減る作業が少なくなってしまう。&lt;/p&gt;
&lt;p&gt;もちろん、この方法が扱いやすい範囲は明確にある。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Simple sites
Docs
Portfolios
Basic blogs
Bootstrap-like layouts
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;難しくなるのは、AstroやHexoで作られたテーマのように、細かなセレクター、疑似要素、レイアウト規則、ブラウザ固有の挙動に依存する場合だ。これは宣言的UIや各フレームワークの欠陥ではなく、抽象化の適用範囲だと思う。&lt;/p&gt;
&lt;h2&gt;Sagaは境界を隠さない&lt;/h2&gt;
&lt;p&gt;Sagaは別の方針を採る。Swiftが得意な部分をSwiftに任せ、ブラウザ側の関心事はWeb本来の形で残す。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Swift:
Content model
Pipeline
Generation logic
Type safety

Web:
HTML structure
CSS styling
JavaScript behavior
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;テンプレートは次のようになる。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;article(class: &quot;mx-auto max-w-3xl px-6 py-12&quot;) {
  h1(class: &quot;text-4xl font-bold tracking-tight&quot;) {
    item.title
  }
  div(class: &quot;prose prose-slate dark:prose-invert&quot;) {
    raw(item.body)
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;これはSwiftUIをブラウザに再現するものではなく、SwiftでHTMLを生成しているだけだ。役割分担も読み取りやすい。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Swift-native:
Types, functions, composition

Web-native:
HTML, CSS, browser semantics
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;今回のようなテーマでは、大きなUI抽象化を追加することより、この直接性のほうが役に立つ。&lt;/p&gt;
&lt;h2&gt;Tailwindを組み合わせると扱いやすい&lt;/h2&gt;
&lt;p&gt;Tailwindを使わないSagaのテンプレートは、名前付きクラスを使う従来のHTMLに近い。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;article(class: &quot;post-card&quot;) {
  h2(class: &quot;post-card__title&quot;) {
    item.title
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Tailwindを使えば、レイアウトとスタイルをHTML生成箇所のすぐそばに置ける。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;article(class: &quot;group rounded-3xl border border-slate-200 bg-white p-6 shadow-sm transition hover:-translate-y-1 hover:shadow-lg&quot;) {
  h2(class: &quot;text-2xl font-semibold tracking-tight&quot;) {
    a(href: post.url) {
      post.title
    }
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;CSSの概念を一つずつSwiftのmodifier APIへ置き換える必要がない。Tailwindはutility classで表現したCSSなので、ブラウザ側のモデルも見失わずに済む。&lt;/p&gt;
&lt;h2&gt;RaptorとIgniteは向いている仕事が違う&lt;/h2&gt;
&lt;p&gt;Raptorは一般的なSSGより広い範囲を扱おうとしており、次のようなサイトモデルを持つ。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Site
Page
PostPage
CategoryPage
Layout
Theme
Style
PostWidget
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;さらにVaporと統合してサーバーサイドレンダリングもできる。Swift中心のコンテンツプラットフォーム、静的・動的ページの混在するサイト、バックエンド統合が必要なプロジェクトなら面白い選択肢になる。&lt;/p&gt;
&lt;p&gt;ただし、このサイトモデルだけで複雑なフロントエンドを簡単に表現できるわけではない。UIとスタイルシステムの範囲を超えると、実装は再び、&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Tag + Div + Class + CSS
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;に戻る。この段階では、上位の抽象化がコンテンツ設計やサーバー統合など別の問題を解決しているかどうかを確認したい。フロントエンドの作業量は、もう減っていないからだ。&lt;/p&gt;
&lt;p&gt;Igniteはもっと現実的な組み合わせを選んでいる。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Swift API + Bootstrap
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;小規模サイト、ポートフォリオ、ドキュメントを短時間で作る用途には向いている。一方、独自のビジュアルを追求すると、Bootstrapの構造や見た目を隠すのが次第に難しくなる。&lt;/p&gt;
&lt;h2&gt;複雑なテーマなら今もAstroを選ぶ&lt;/h2&gt;
&lt;p&gt;Swiftを使うこと自体が要件でなければ、今回のような複雑なビジュアルテーマではAstroが最も安定した選択だと思う。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;HTML、CSS、JavaScriptを第一級の要素として扱える&lt;/li&gt;
&lt;li&gt;コンポーネントがブラウザのプリミティブに近い&lt;/li&gt;
&lt;li&gt;Tailwindを素直に統合できる&lt;/li&gt;
&lt;li&gt;Content Collectionsでコンテンツを構造化できる&lt;/li&gt;
&lt;li&gt;エコシステムが成熟している&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;SwiftでWebサイトを作れないという意味ではない。Swiftによる抽象化のコストに対して、プロジェクトが必要とする価値を得られるかどうかが重要になる。&lt;/p&gt;
&lt;p&gt;今回比べた二つの方向性は、次のように整理できる。&lt;/p&gt;
&lt;h3&gt;SwiftUI風のWeb DSL（Raptor / Ignite）&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;Swift expresses UI
→ translated into HTML/CSS
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Swiftネイティブ生成 + WebネイティブUI（Saga）&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;Swift handles logic and structure
HTML/CSS/JS express the UI
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;見た目を細かく作り込むサイトなら、私は後者を選ぶ。型、関数、合成、コンテンツモデル、生成ロジックにはSwiftを使い、UIはHTML、CSS、JavaScriptに任せる。&lt;/p&gt;
&lt;p&gt;用途ごとの候補は次のようになった。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Not using Swift: Astro + Tailwind
Using Swift seriously: Saga + Tailwind
Quick Swift site: Ignite
Exploring Swift Web frameworks: Raptor
&lt;/code&gt;&lt;/pre&gt;
</content:encoded></item><item><title>Swiftで個人サイトを作る</title><link>https://www.shiinayane.com/ja/posts/building-personal-website-in-swift/</link><guid isPermaLink="true">https://www.shiinayane.com/ja/posts/building-personal-website-in-swift/</guid><description>簡単だと思っていた……実際にやってみるまでは</description><pubDate>Tue, 21 Apr 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Raptorでこのサイトを作っているとき、記事の日付、ナビゲーション、タグなどに個別のスタイルが必要になりました。テーマの設定を少し足せば済むと思っていたのですが、気づけばフレームワークの内部まで変更していました。しかも最後に分かったのは、機能が足りなかったのではなく、使うべき抽象を間違えていたということでした。&lt;/p&gt;
&lt;p&gt;RaptorはSwiftで書かれた静的サイトジェネレーターです。HTMLテンプレートやJSXの代わりに、レイアウトもSwiftで記述します。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;VStack {
    Text(&quot;Hello, world!&quot;)
    Text(&quot;Welcome to my site&quot;)
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;タイポグラフィや色には、組み込みのテーマ機構を使えます。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;.font(.title1)
.fontSize(36, for: .title1)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;記述は分かりやすく、型安全です。普段使っているSwiftでサイトを書くのも素直に楽しかったのですが、組み込みの役割だけでは実際のデザインを表現しきれなくなりました。&lt;/p&gt;
&lt;h2&gt;実際のサイトには細かなテキストがある&lt;/h2&gt;
&lt;p&gt;Raptorのタイポグラフィには、次の役割が用意されています。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;body&lt;/li&gt;
&lt;li&gt;title1 … title6&lt;/li&gt;
&lt;li&gt;codeBlock&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;しかし、サイトには本文と見出し以外にも、投稿日や著者、ナビゲーション、タグ、カテゴリー、ボタン、リンクといったテキストがあります。&lt;/p&gt;
&lt;p&gt;HugoやHexo、通常のHTMLテンプレートなら、classを付けてCSSを書けば終わる話です。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;span class=&quot;post-meta&quot;&amp;gt;April 20&amp;lt;/span&amp;gt;
&amp;lt;a class=&quot;nav-label&quot;&amp;gt;Archive&amp;lt;/a&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;.post-meta {
  font-size: 12px;
  color: gray;
}
.nav-label {
  font-weight: bold;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;一方、Raptorには次のような組み込みの役割はありません。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;.postMeta
.navLabel
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;そこで最初は、独自のテキストロールを追加しようと考えました。&lt;/p&gt;
&lt;h2&gt;Themeを拡張してみる&lt;/h2&gt;
&lt;p&gt;目指したAPIは次のようなものです。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Text(&quot;April 20&quot;).textRole(.postMeta)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Theme側でロールごとの設定を書きます。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;.fontSize(12, for: .postMeta)
.fontWeight(.medium, for: .postMeta)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;そこからCSSを生成し、&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;.text-role-post-meta {
  font-size: 12px;
  font-weight: 500;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;最終的なHTMLにclassを付与します。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;p class=&quot;text-role-post-meta&quot;&amp;gt;April 20&amp;lt;/p&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;フレームワークを調べながら、テーマ設定、CSS生成、レンダリング処理に手を入れました。実装自体は動きました。独自ロールがテーマ機構を通り、ライト・ダークモードに対応し、CSSが自動生成され、HTMLにも反映されるようになりました。&lt;/p&gt;
&lt;p&gt;ただし、設計には違和感が残りました。&lt;/p&gt;
&lt;p&gt;まず、テキストを表す方法が二つに分かれます。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;.font(.title1)        // built-in
.textRole(.postMeta)  // custom
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;しかも、この二つは責務が同じではありません。Raptorの&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;.font(.title1)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;は、&lt;code&gt;&amp;lt;h1&amp;gt;&lt;/code&gt;のようなHTMLタグを決めると同時にスタイルも適用します。それに対して、追加した&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;.textRole(.navLabel)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;が変えるのはスタイルだけです。HTMLの構造と見た目は別の関心事なのに、その区別を整理しないまま新しいタイポグラフィAPIを足してしまいました。&lt;/p&gt;
&lt;p&gt;結局、コードは次のようになります。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;.tag(.h1)
.textRole(.navLabel)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;表しているものは、ほぼこれと同じです。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;h1 class=&quot;nav-label&quot;&amp;gt;&amp;lt;/h1&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;型安全なAPIで複雑さを減らすはずが、手順を増やしてHTMLを作り直しているだけでした。&lt;/p&gt;
&lt;h2&gt;すでにあったStyleという答え&lt;/h2&gt;
&lt;p&gt;ほかの静的サイトジェネレーターも確認しました。たとえばHugoは、この問題をフレームワークの役割として抽象化せず、&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;p class=&quot;post-meta&quot;&amp;gt;&amp;lt;/p&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;と書いて、残りをCSSに任せます。&lt;/p&gt;
&lt;p&gt;この比較をきっかけにRaptorの設計とソースコードを読み直すと、役割分担が見えてきました。Themeはタイポグラフィ、色、余白などのグローバルなデザイントークンを定義するものです。「記事メタデータ」のような意味を持つスタイルを、Themeの新しいテキストロールにする必要はありません。&lt;/p&gt;
&lt;p&gt;Raptorには、そのための&lt;code&gt;Style&lt;/code&gt;がすでに用意されていました。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;struct PostMetaStyle: Style {
    func style(content: Content, environment: EnvironmentConditions) -&amp;gt; Content {
        content
            .font(.caption)
            .foregroundStyle(.secondary)
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;コンテンツには次のように適用できます。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Text(&quot;April 20&quot;)
    .style(PostMetaStyle())
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;役割としては、&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;p class=&quot;post-meta&quot;&amp;gt;April 20&amp;lt;/p&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;と同じですが、Swiftのまま再利用、合成でき、型安全性も保てます。&lt;/p&gt;
&lt;p&gt;さらに&lt;code&gt;Style&lt;/code&gt;は、ライト・ダークモード、現在のテーマ、コントラスト設定、レイアウト条件などの環境情報を参照できます。意味を持つスタイルをテーマのロールに追加しなくても、環境に応じた見た目を定義できます。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;struct PostMetaStyle: Style {
    func style(content: Content, environment: EnvironmentConditions) -&amp;gt; Content {
        if environment.colorScheme == .dark {
            content.foregroundStyle(.gray)
        } else {
            content.foregroundStyle(.secondary)
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;見落としていた境界は単純でした。Themeはグローバルなデザイントークンを定義し、&lt;code&gt;Style&lt;/code&gt;は再利用できる意味的なスタイルをまとめます。欲しかった機能は最初からRaptorにあり、探す場所を間違えていただけでした。&lt;/p&gt;
</content:encoded></item><item><title>SwiftUI で Apple Music 風の消えるナビゲーションタイトルを再現する</title><link>https://www.shiinayane.com/ja/posts/apple-music-style-navigation-titles/</link><guid isPermaLink="true">https://www.shiinayane.com/ja/posts/apple-music-style-navigation-titles/</guid><pubDate>Fri, 03 Apr 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;iOS 26 の Apple Music では、スクロールすると大きなナビゲーションタイトルとツールバーの内容が見えなくなる。iOS 11 以降の一般的な large title の動きとは少し違う。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./assets/apple-music-style-navigation-titles/navigation-title-collapse.png&quot; alt=&quot;Apple Music での表示例&quot; /&gt;&lt;/p&gt;
&lt;p&gt;従来の遷移は次のようなものだった。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Large Title
   ↓ scroll
Small Navigation Title
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Apple Music の表示は、こちらに近い。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Large Title
   ↓ scroll
(no title)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;この挙動をそのまま有効にする SwiftUI の公開 modifier は、今のところ用意されていない。再現方法は、スクロール位置から独自の header を制御する方法と、compact title だけを空にする簡単な方法に分けられる。&lt;/p&gt;
&lt;h2&gt;header 全体を制御する&lt;/h2&gt;
&lt;p&gt;きちんと作るなら、必要になるのはだいたい次の要素だ。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;safeAreaInset(edge: .top)&lt;/code&gt; に配置する独自 header&lt;/li&gt;
&lt;li&gt;&lt;code&gt;ScrollGeometry&lt;/code&gt; を使ったスクロール位置の検出&lt;/li&gt;
&lt;li&gt;タイトルとほかの toolbar item を分けた表示制御&lt;/li&gt;
&lt;li&gt;opacity や offset のアニメーション&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;SwiftUI 側の部品はすでに揃っている。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;.onScrollGeometryChange(...)
.safeAreaInset(...)
.toolbar(...)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;ただし、専用のナビゲーションバー設定ではなく、あくまで低レベルな部品だ。単純な例なら、縦方向の offset が一定値を超えたところで header を隠せばよい。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;struct CollapsingHeaderView: View {

    @State private var headerHidden = false

    var body: some View {
        ScrollView {
            VStack {
                ForEach(0..&amp;lt;50) { i in
                    Text(&quot;Row \(i)&quot;)
                        .frame(maxWidth: .infinity)
                        .padding()
                }
            }
        }
        .onScrollGeometryChange(for: CGFloat.self) { geometry in
            geometry.contentOffset.y
        } action: { _, offset in
            headerHidden = offset &amp;gt; 40
        }
        .safeAreaInset(edge: .top) {
            header
                .opacity(headerHidden ? 0 : 1)
                .animation(.easeInOut, value: headerHidden)
        }
    }

    private var header: some View {
        HStack {
            Text(&quot;Library&quot;)
                .font(.largeTitle.bold())

            Spacer()

            Image(systemName: &quot;person.crop.circle&quot;)
        }
        .padding()
        .background(.ultraThinMaterial)
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;ScrollView&lt;/code&gt;、&lt;code&gt;List&lt;/code&gt;、&lt;code&gt;LazyVStack&lt;/code&gt; のどれを使う場合でも応用でき、header のレイアウトや隠すタイミング、アニメーションも自由に決められる。&lt;/p&gt;
&lt;p&gt;一方で、スクロール方向、しきい値、ツールバーの配置、refreshable との組み合わせ、入れ子になった &lt;code&gt;NavigationStack&lt;/code&gt; の edge case まで、アプリ側で面倒を見ることになる。画面全体の挙動を正確に合わせたいなら妥当だが、compact title を消したいだけなら少し大げさだ。&lt;/p&gt;
&lt;h2&gt;compact title だけなら principal を空にする&lt;/h2&gt;
&lt;p&gt;もっと簡単な方法は、principal の toolbar item に空のタイトルを置くことだ。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;.toolbar {
    ToolbarItem(placement: .principal) {
        Text(&quot;&quot;)
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;通常の large title はそのまま設定する。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;.navigationTitle(&quot;Library&quot;)
.navigationBarTitleDisplayMode(.large)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;すると、見た目の遷移は次のようになる。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Large Title
   ↓ scroll
(empty)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;リスト上部では大きなタイトルが表示され、折りたたまれた後は principal item の空文字列が compact title になる。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;NavigationStack {
    List(items) { item in
        Text(item.title)
    }
    .navigationTitle(&quot;Library&quot;)
    .navigationBarTitleDisplayMode(.large)
    .toolbar {
        ToolbarItem(placement: .principal) {
            Text(&quot;&quot;)
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;スクロール状態を追加しなくても、標準の &lt;code&gt;NavigationStack&lt;/code&gt; だけで Apple Music にかなり近い見た目になる。&lt;/p&gt;
&lt;h2&gt;実際に消えているのはタイトルの内容だけ&lt;/h2&gt;
&lt;p&gt;SwiftUI のナビゲーションタイトルは、通常この 2 つの状態を行き来する。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Large Navigation Title
        ↓
Compact Navigation Title
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;principal toolbar item は compact 側に表示される内容を置き換える。そこを空にすると、&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Large Title
   ↓
Small Title
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;だったものが、&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Large Title
   ↓
(blank space)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;になる。&lt;/p&gt;
&lt;p&gt;ナビゲーションバー自体が削除されたわけではない。compact title の中身が何も描画されないため、header が消えたように見えている。&lt;/p&gt;
&lt;p&gt;この違いから、制約もはっきりしている。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;ナビゲーションバーは残っている&lt;/li&gt;
&lt;li&gt;ほかの toolbar item は引き続きスペースを使う場合がある&lt;/li&gt;
&lt;li&gt;現在の SwiftUI の描画挙動に依存するため、将来のバージョンで変わる可能性がある&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;header とそのレイアウト全体を消す必要があるなら、スクロール位置を使う実装を選ぶ。compact title が空になれば十分なら、principal item の方法で済ませるのが軽い。&lt;/p&gt;
&lt;h2&gt;将来は公式 API になるかもしれない&lt;/h2&gt;
&lt;p&gt;Apple は、システムアプリで先に UI を導入し、その後に関連する公開 API を提供することがある。&lt;code&gt;.searchable&lt;/code&gt;、large navigation title、Apple Music で使われているタブバーの最小化も、その流れで登場した例だ。&lt;/p&gt;
&lt;p&gt;将来の SwiftUI に、たとえば次のような API が追加される可能性はある。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;.navigationBarCollapseBehavior(.onScroll)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;あるいは、&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;.toolbarScrollVisibility(.hidden)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;もちろん、この 2 つは API の形を説明するための例であり、現時点では存在しない。&lt;/p&gt;
</content:encoded></item><item><title>DockerでMinecraftサーバーを構築する</title><link>https://www.shiinayane.com/ja/posts/deploy-minecraft-server-with-docker/</link><guid isPermaLink="true">https://www.shiinayane.com/ja/posts/deploy-minecraft-server-with-docker/</guid><pubDate>Thu, 15 Jan 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;最近、MacでMinecraftサーバーを立てたくなりました。&lt;/p&gt;
&lt;p&gt;サーバーのディレクトリで&lt;code&gt;./run.bash&lt;/code&gt;を実行するだけなら簡単ですが、ターミナルとJavaプロセスをずっとバックグラウンドで動かしておくのは、さすがにあまりスマートではありません。Screenを使えばターミナルは隠せますが、Javaサービス自体の状態までは管理してくれません。&lt;/p&gt;
&lt;p&gt;Dockerならこういうサービスも簡単にデプロイできるので、Minecraftサーバーにも使えないかと思って調べてみました。&lt;/p&gt;
&lt;p&gt;そして運よく、&lt;code&gt;itzg/minecraft-server&lt;/code&gt;がありました！&lt;/p&gt;
&lt;h2&gt;概要&lt;/h2&gt;
&lt;p&gt;GitHubはこちら：&lt;/p&gt;
&lt;p&gt;::github{repo=&quot;itzg/docker-minecraft-server&quot;}&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://docker-minecraft-server.readthedocs.io/en/latest/&quot;&gt;ドキュメント&lt;/a&gt;には、次のように紹介されています。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;このDockerイメージは、起動時にMinecraft Serverの最新安定版を自動でダウンロードします。特定のバージョンや最新スナップショットを実行したり、アップグレードしたりすることもできます。詳しくはVersionsのセクションを参照してください。&lt;/p&gt;
&lt;p&gt;最新の安定版を使うだけなら、次のコマンドを実行します。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;pre&gt;&lt;code&gt;docker run -d -it -p 25565:25565 -e EULA=TRUE itzg/minecraft-server
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;この場合、標準のサーバーポート&lt;code&gt;25565&lt;/code&gt;がホスト側に公開されます。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;Docker Compose&lt;/h2&gt;
&lt;p&gt;上のコマンドで直接イメージを起動するより、ほかの一般的なプロジェクトと同じくDocker Composeを使うほうがおすすめです。&lt;/p&gt;
&lt;p&gt;公式の手順は次のとおりです。&lt;/p&gt;
&lt;blockquote&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;新しいディレクトリを作成する&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;下の内容を&lt;code&gt;compose.yaml&lt;/code&gt;というファイル名で保存する&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;そのディレクトリで&lt;code&gt;docker compose up -d&lt;/code&gt;を実行する&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;完了！クライアントからホスト名またはIPアドレスとポート&lt;code&gt;25565&lt;/code&gt;を指定して接続する&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/blockquote&gt;
&lt;pre&gt;&lt;code&gt;# docker.yaml
services:
  mc:
    image: itzg/minecraft-server:latest
    pull_policy: daily
    tty: true
    stdin_open: true
    ports:
      - &quot;25565:25565&quot;
    environment:
      EULA: &quot;TRUE&quot;
    volumes:
      # attach the relative directory &apos;data&apos; to the container&apos;s /data path
      - ./data:/data
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;Composeファイルを変更した後は、もう一度&lt;code&gt;docker compose up -d&lt;/code&gt;を実行すれば反映されます。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;docker compose logs -f&lt;/code&gt;でログを追い、&lt;code&gt;docker compose ps&lt;/code&gt;で状態を確認し、&lt;code&gt;docker compose stop&lt;/code&gt;でコンテナを停止できます。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Dockerを使い始めたばかりだと、volumesが少し分かりにくいかもしれません。実際はそれほど複雑ではなく、ディレクトリ構成は例えば次のようになります。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Your-MC-Server-Folder
├── data
│   ├── config
│   ├── eula.txt
│   ├── kubejs
│   ├── mods
│   ├── server.properties
│   └── world
└── docker-compose.yml
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;Your-MC-Server-Folder&lt;/code&gt;の中にある&lt;code&gt;data&lt;/code&gt;が、Modやワールドデータを置く場所です。先にDocker Composeを実行してコンテナ側でデータを生成してもいいですし、既存のデータを&lt;code&gt;data&lt;/code&gt;へ移動しても構いません。&lt;/p&gt;
&lt;h2&gt;LoaderとMod&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;compose.yaml&lt;/code&gt;の変数を変更するだけで、Mod入りのサーバーも起動できます。以下は自分が使っている設定です。&lt;a href=&quot;https://setupmc.com/java-server/&quot;&gt;SetupMC&lt;/a&gt;を使って設定を生成することもできます。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;services:
  mc:
    image: itzg/minecraft-server:latest
    tty: true
    stdin_open: true
    ports:
      - &quot;25565:25565&quot;
      - &quot;24454:24454/udp&quot;
    environment:
      EULA: &quot;TRUE&quot;
      TYPE: &quot;NEOFORGE&quot;
      VERSION: &quot;1.21.1&quot;
      INIT_MEMORY: &quot;4G&quot;
      MAX_MEMORY: &quot;12G&quot;
      MOTD: &quot;A Minecraft Server&quot;
      TZ: &quot;Asia/Tokyo&quot;
      DIFFICULTY: &quot;hard&quot;
    volumes:
      - &quot;./data:/data&quot;

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;ちなみに、&lt;code&gt;24454/udp&lt;/code&gt;はMod「Simple Voice Chat」のために開けています。この場合、Simple Voice Chatの設定でもIPを&lt;code&gt;0.0.0.0&lt;/code&gt;に変更する必要があります。&lt;/p&gt;
&lt;h2&gt;それでは……&lt;/h2&gt;
&lt;p&gt;楽しんで！&lt;/p&gt;
</content:encoded></item><item><title>グラフアルゴリズム</title><link>https://www.shiinayane.com/ja/posts/graph-algorithms/</link><guid isPermaLink="true">https://www.shiinayane.com/ja/posts/graph-algorithms/</guid><pubDate>Fri, 21 Feb 2025 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;基本的なグラフアルゴリズム&lt;/h2&gt;
&lt;h3&gt;幅優先探索 (BFS)&lt;/h3&gt;
&lt;h4&gt;擬似コード&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;BFS(G, s)
    for 各頂点 u ∈ G.V - {s}
        u.color = WHITE
        u.d = ∞
        u.π = NIL
    s.color = GRAY
    s.d = 0
    s.π = NIL
    Q = ∅
    ENQUEUE(Q, s)
    while Q != ∅
        u = DEQUEUE(Q)
        for 各頂点 v ∈ G.Adj[u] // uの近傍を探索する
            if v.color == WHITE // vが今発見されている？
                v.color = GRAY
                v.d = u.d + 1
                v.π = u
                ENQUEUE(Q, v)   // vが今最前線上にある
        u.color = BLACK         // uは今最前線の後方にある
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;手続きPRINT-PATHは、sからvへの最短路上の頂点をプリントする&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;PRINT-PATH(G, s, v)
    if v == s
        sをプリントする
    elseif v.π == NIL
        s “から” v “への経路は存在しない”をプリントする
    else PRINT-PATH(G, s, v.π)
        vをプリントする
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;compute a path in $G$ that traverses each edge in $E$ exactly once in each direction&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;MAKE-PATH(u)
    for each v ∈ Adj[u] but not in the tree such that u ≤ v
        go to v and back to u
    for each v ∈ Adj[u] but not equal to u.π
        go to v
        perform the path proscribed by MAKE-PATH(v)
    go to u.π
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;解析&lt;/h4&gt;
&lt;p&gt;初期化後は頂点を白にすることはない。したがって、第 13 行の判定から、各頂点は高々 1 回だけキューに挿入され、したがって高々 1 回だけキューから削除される。
キューに対する挿入と削除は $O(1)$ 時間で実行できるので、キュー操作に費やせる時間は全体で $O(V)$ である。
また各頂点をキューから削除したときにだけその頂点の隣接リストを走査するので、各頂点の隣接リストを走査する回数は高々 1 回である。
すべての隣接リストの長さの総和は $O(E)$ なので、隣接リストの走査に必要な時間は $O(V + E)$ である。
初期化のためのオーバーヘッドは $O(V)$ なので、したがって、幅優先探索は $G$ の隣接リスト表現のサイズの線形時間で走る。&lt;br /&gt;
BFS の総実行時間は &lt;strong&gt;$O(V + E)$&lt;/strong&gt; である。&lt;/p&gt;
&lt;h3&gt;深さ優先探索 (DFS)&lt;/h3&gt;
&lt;h4&gt;擬似コード&lt;/h4&gt;
&lt;p&gt;ccは連結成分を出力するために用いる変数である&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;DFS(G)
    for 各頂点 u ∈ G.V
        u.color = WHITE
        u.π = NIL
    time = 0
    // cc = 1
    for 各頂点 u ∈ G.V
        if u.color == WHITE
            // u.cc = cc
            // cc = cc + 1
            DFS-VISIT(G, u)
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;DFS-VISIT(G, u)
    time = time + 1     // 白頂点uが今発見された
    u.d = time
    u.color = GRAY
    for 各頂点 v ∈ G.Adj[u]    // 各辺(u, v)を探索する
        if v.color == WHITE
            // v.cc = u.cc
            v.π = u
            DFS-VISIT(G, v)
    time = time + 1
    u.f = time
    u.color = BLACK     // uを黒に彩色する；uの探索が終了した
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;再帰を使わず、スタックを用いるDFS&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;DFS-STACK(G)
    for each vertex u ∈ G.V
        u.color = WHITE
        u.π = NIL
    time = 0
    for each vertex u ∈ G.V
        if u.color == WHITE
            DFS-VISIT-STACK(G, u)
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;DFS-VISIT-STACK(G, u)
    S = Ø
    PUSH(S, u)
    time = time + 1             // 白頂点uが今発見された
    u.d = time
    u.color = GRAY
    while !STACK-EMPTY(S)
        u = TOP(S)
        v = FIRST-WHITE-NEIGHBOR(G, u)
        if v == NIL
            // uの隣接リストは十分に探索された
            POP(S)
            time = time + 1
            u.f = time
            u.color = BLACK     // uを黒に彩色する；uの探索が終了した
        else
            // uの隣接リストはまだ探索されていない
            v.π = u
            time = time + 1
            v.d = time
            v.color = GRAY
            PUSH(S, v)
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;FIRST-WHITE-NEIGHBOR(G, u)
    for each vertex v ∈ G.Adj[u]
        if v.color == WHITE
            return v
    return NIL
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;有向グラフGの全ての辺をその種類と共に印刷するバージョン&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;DFS-VISIT-PRINT(G, u)
    time = time + 1
    u.d = time
    u.color = GRAY
    for each vertex v ∈ G.Adj[u]
        if v.color == WHITE
            print &quot;(u, v) is a tree edge.&quot;
            v.π = u
            DFS-VISIT-PRINT(G, v)
        else if v.color == GRAY
            print &quot;(u, v) is a back edge.&quot;
        else if v.d &amp;gt; u.d
            print &quot;(u, v) is a forward edge.&quot;
        else
            print &quot;(u, v) is a cross edge.&quot;
    u.color = BLACK
    time = time + 1
    u.f = time
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;解析&lt;/h4&gt;
&lt;p&gt;DFS の実行時間はどのくらいだろうか？ DFS の第 1〜3 行と第 5〜7 行のループは、DFS-VISIT の呼出しに必要な時間を除くと $\Theta(V)$ 時間かかる。幅優先探索と同様、ここでも集計法を用いて解析する。
DFS-VISIT 呼出しが起こる頂点 $u$ はつねに白であり、DFS-VISIT の最初の仕事は $u$ を灰に彩色することで、手続き DFS-VISIT は各頂点 $u \in V$ に対してちょうど 1 回ずつ呼び出される。
DFS-VISIT(G, u) を実行中、第 4〜7 行の繰返し回数は $|Adj[u]|$ である。&lt;/p&gt;
&lt;p&gt;$$
\sum_{v \in V} |Adj[v]| = \Theta(E)
$$
であり、DFS-VISIT は各頂点につき 1 回呼び出されるので、DFS-VISIT の第 4〜7 行の実行にかかる総時間は $\Theta(V + E)$ である。
したがって、DFS の実行時間は &lt;strong&gt;$\Theta(V + E)$&lt;/strong&gt; である。&lt;/p&gt;
&lt;h3&gt;トポロジカルソート&lt;/h3&gt;
&lt;h4&gt;擬似コード&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;TOPOLOGICAL-SORT(G)
    各頂点vの終了時刻v.fを計算するためにDFS(G)を呼び出し
    各頂点の探索が終了するたびに、この頂点を連結リストの先頭に挿入する
    return 頂点の連結リスト
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;有向非巡回グラフ$G = (V, E)$と2頂点a, bを入力とし、Gにおけるaからbへの単純路の個数を返す線形時間アルゴリズム&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;SIMPLE-PATHS(G, u, v)
    TOPOLOGICAL-SORT(G)
    let {v[1], v[2]..v[k - 1]} be the vertex between u and v
    v[0] = u
    v[k] = v
    for j = 0 to k - 1
        DP[j] = ∞
    DP[k] = 1
    return SIMPLE-PATHS-AID(G, DP, 0)
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;SIMPLE-PATHS-AID(G, DP, i)
    if i &amp;gt; k
        return 0
    else if DP[i] != ∞
        return DP[i]
    else
       DP[i] = 0
       for v[m] in G.adj[v[i]] and 0 &amp;lt; m ≤ k
            DP[i] += SIMPLE-PATHS-AID(G, DP, m)
       return DP[i]
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;解析&lt;/h4&gt;
&lt;p&gt;深さ優先探索に $\Theta(V + E)$ 時間かかり、$|V|$ 個の頂点のそれぞれを連結リストの先頭に挿入するのに $O(1)$ 時間しかかからないので、
TOPOLOGICAL-SORT は &lt;strong&gt;$\Theta(V + E)$&lt;/strong&gt; 時間で実行できる。&lt;/p&gt;
&lt;h3&gt;強連結成分&lt;/h3&gt;
&lt;h4&gt;擬似コード&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;STRONGLY-CONNECTED-COMPONENTS(G)
    DFS(G)を呼び出し、各頂点uに対して終了時刻u.fを計算する
    G^Tを生成する
    DFS(G^T)を呼び出しが、DFSの主ループでは（第1行で計算した）u.fの降順で頂点を探索する
    第3行で生成した深さ優先森の各木の頂点を、それぞれ分離された強連結成分として出力する

&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;最小全域木&lt;/h2&gt;
&lt;h3&gt;Kruskalのアルゴリズム&lt;/h3&gt;
&lt;h4&gt;擬似コード&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;MST-KRUSKAL(G, w)
    A = ∅
    for 各頂点 v ∈ G.V
        MAKE-SET(v)
    G.Eの辺を含む1つのリストを生成する
    重みwの単調増加順にG.Eの辺のリストをソートする
    for ソートされたリストから順に各辺(u, v) ∈ G.E
        if FIND-SET(u) != FIND-SET(v)
            A = A ∪ {(u, v)}
            UNION(u, v)
    return A
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;解析&lt;/h4&gt;
&lt;p&gt;グラフ $G = (V, E)$ に対する Kruskal のアルゴリズムの実行時間は、互いに素な集合族のためのデータ構造の実装方法に依存する。現在知られている中で漸近的に最速の方法なので、第 19.3 節で述べた 2 つのヒューリスティック、ランクによる合併と経路圧縮を併用する互いに素な集合の森による実装方法を仮定する。&lt;/p&gt;
&lt;p&gt;第 1 行で $A$ の初期化に $O(1)$ 時間、第 4 行で 1 つの辺のリストの生成に $O(V + E)$ 時間 ($G$ が連結なので、
これは $O(E)$ である)、そして第 5 行で辺をソートするのに $O(E \lg E)$ 時間かかる。(第 2〜3 行の for ループにおける $|V|$ 回の MAKE-SET 操作のコストはすでに説明済みである。)&lt;/p&gt;
&lt;p&gt;第 6〜9 行の for ループでは互いに素な集合の森に対して $O(E)$ 回の FIND-SET と UNION 操作を行う。
$|V|$ 回の MAKE-SET 操作を含めて、これらの操作には全体で合計 $O((V + E) \alpha(V))$ 時間かかる。ただし、$\alpha$ は第 19.4 節 (148 ページ) で定義した非常にゆっくりと増加する関数である。
$G$ が連結なので $\alpha(|V|) = O(\lg V) = O(\lg E)$ である。&lt;/p&gt;
&lt;p&gt;さらに、$|E| &amp;lt; |V|^2$ に注意すると、Kruskal のアルゴリズムの総実行時間は $O(E \lg E)$ である。
したがって、Kruskal のアルゴリズムの総実行時間を &lt;strong&gt;$O(E \lg V)$&lt;/strong&gt; と書き直すことができる。&lt;/p&gt;
&lt;h3&gt;Primのアルゴリズム&lt;/h3&gt;
&lt;h4&gt;擬似コード&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;MST-PRIM(G, w, r)
    for 各頂点 u ∈ G.V
        u.key = ∞
        u.π = NIL
    r.key = 0
    Q = ∅
    for 各頂点 u ∈ G.V
        INSERT(G, u)
    while Q != ∅
        u = EXTRACT-MIN(Q)
        for G.Adj[u]の各頂点v
            if v ∈ Q and w(u, v) &amp;lt; v.key
                v.π = u
                v.key = w(u, v)
                DECREASE-KEY(Q, v, w(u, v))
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;グラフ$G = (V, E)$が隣接行列によって与えられるとき、$O(V^2)$時間で走るPrimのアルゴリズム&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;PRIM-ADJ(G, w, r)
    initialize A with every entry = (NIL, ∞)
    T = {r}
    for i = 1 to V
        if Adj[r, i] != 0
            A[i] = (r, w(r, i))
    while T != V
        min = ∞
        for each v in V - T
            if A[v].2 &amp;lt; min
                min = A[v].2
                k = v
        T = T ∪ {k}
        k.π = A[k].1
        for i = 1 to V
            if Adj[k, i] != 0 and i ∉ T and w(k, i) &amp;lt; A[i].2
                A[i] = (k, w(k, i))
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;フィボナッチヒープを用いるPrimアルゴリズム&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;MST-REDUCE(G, T)
    for each v ∈ G.V
        v.mark = false
        MAKE-SET(v)
    for each u ∈ G.V
        if u.mark == false
            choose v ∈ G.Adj[u] such that (u, v).c is minimized
                UNION(u, v)
                T = T ∪ {(u, v).orig}
                u.mark = v.mark = true
    G`.V = {FIND-SET(v): v ∈ G.V}
    G`.E = Ø
    for each (x, y) ∈ G.E
        u = FIND-SET(x)
        v = FIND-SET(y)
        if (u, v) ∉ G`.E
             G`.E = G`.E ∪ {(u, v)}
             (u, v).orig` = (x, y).orig
             (u, v).c` = (x, y).c
        else if (x, y).c &amp;lt; (u, v).c`
             (u, v).orig` = (x, y).orig
             (u, v).c` = (x, y).c
    construct adjacency lists G`.Adj for G`
    return G` and T
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;解析&lt;/h4&gt;
&lt;p&gt;Prim のアルゴリズムの実行時間は min 優先度つきキュー $Q$ の実装方法に依存する。2 分 min ヒープ (第 1 巻第 6 章 (ヒープソート) 参照) を用いて、頂点と対応するヒープ要素の対応づけの方法も含めて実装できる。&lt;/p&gt;
&lt;p&gt;手続き BUILD-MIN-HEAP は第 5 行を $O(V)$ 時間で実行できる。実際、BUILD-MIN-HEAP を呼び出す必要すらない。
すべてのキーを min ヒープの根に置きさえすれば、それ以外のキーはすべて $\infty$ なので、それらは min ヒープのどこに置いてもよい。
while ループの本体は $|V|$ 回繰り返され、EXTRACT-MIN 操作には $O(\lg V)$ 時間かかるので、EXTRACT-MIN の呼び出しにかかる総時間は $O(V \lg V)$ である。
全隣接リストの長さの合計は $2|E|$ なので、第 10〜14 行の for ループは合計 $O(E)$ 回繰り返される。
for ループの中で第 11 行が行う、与えられた頂点が $Q$ に属するか否かの判定は、各頂点について $Q$ に属するか否かを示す 1 ビットを用い、頂点を $Q$ から削除したときにそのビットを更新すれば、定数時間で実行できる。
第 14 行の DECREASE-KEY 操作の各呼び出しは $O(\lg V)$ 時間で実行できる。&lt;/p&gt;
&lt;p&gt;したがって、Prim のアルゴリズムで要する総時間は &lt;strong&gt;$O(V \lg V + E \lg V) = O(E \lg V)$&lt;/strong&gt; となり、これは本書の Kruskal のアルゴリズムの実装と漸近的に等しい。&lt;/p&gt;
&lt;h2&gt;単一始点最短路&lt;/h2&gt;
&lt;h3&gt;Bellman-Fordのアルゴリズム&lt;/h3&gt;
&lt;h4&gt;擬似コード&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;BELLMAN-FORD(G, w, s)
    INITIALIZE-SINGLE-SOURCE(G, s)
    for i = 1 to |G.V| - 1
        for 各辺 (u, v) ∈ G.E
            RELAX(u, v, w)
    for 各辺 (u, v) ∈ G.E
        if v.d &amp;gt; u.d + w(u, v)
            return FALSE
    return TRUE
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;始点から頂点vへのある経路上に負閉路があるとき、v.d値として-∞を設定するバージョン&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;BELLMAN-FORD`(G, w, s)
    INITIALIZE-SINGLE-SOURCE(G, s)
    for i = 1 to |G.V| - 1
        for each edge (u, v) ∈ G.E
            RELAX(u, v, w)
    for each edge(u, v) ∈ G.E
        if v.d &amp;gt; u.d + w(u, v)
            mark v
    for each vertex u ∈ marked vertices
        DFS-MARK(u)
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;DFS-MARK(u)
    if u != NIL and u.d != -∞
        u.d = -∞
        for each v in G.Adj[u]
            DFS-MARK(v)
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;解析&lt;/h4&gt;
&lt;p&gt;グラフが隣接リスト形式で表現されているとき、Bellman-Ford のアルゴリズムは &lt;strong&gt;$O(VE)$&lt;/strong&gt; 時間で動作する。&lt;/p&gt;
&lt;p&gt;なぜなら、第 1 行の初期化に $\Theta(V)$ 時間、第 2〜4 行で実行される $|V| - 1$ 回の辺の走査に $\Theta(V + E)$ 時間 ($|E|$ 個の辺を見ているのに $|V|$ 個の隣接リストをチェックする)、
第 5〜7 行の for ループに $O(VE)$ 時間を要するからである。&lt;/p&gt;
&lt;p&gt;$|V| - 1$ より少ない回数の走査で十分なことがある。(練習問題 22.1-3 参照)。これが、$\Theta(V^2 + VE)$ よりも $O(V^2 + VE)$ を要すると主張する理由である。&lt;/p&gt;
&lt;p&gt;しかし $|E| = \Omega(V)$ だが、この場合は、このアルゴリズムの実行時間は $O(VE)$ と評価される。
練習問題 22.1-5 では、$|E| = O(V)$ のときでさえ、Bellman-Ford のアルゴリズムを $O(VE)$ 時間で動作させることを求めている。&lt;/p&gt;
&lt;h3&gt;Dijkstraのアルゴリズム&lt;/h3&gt;
&lt;h4&gt;擬似コード&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;DIJKSTRA(G, w, s)
    INITIALIZE-SINGLE-SOURCE(G, s)
    S = ∅
    Q = ∅
    for 各頂点 u ∈ G.V
        INSERT(Q, u)
    while Q != ∅
        u = EXTRACT-MIN(Q)
        S = S ∪ {u}
        for 各頂点 v ∈ G.Adj[u]
            RELAX(u, v, w)
            if RELAXがv.d値を減少させる
                DECREASE-KEY(Q, v, v.d)
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;解析&lt;/h4&gt;
&lt;p&gt;Dijkstra のアルゴリズムは、どれほど速いのだろうか？
アルゴリズムは min 優先度つきキュー Q を 3 つの優先度つきキュー操作の呼び出しで管理している：
すなわち、（第 5 行の）INSERT、（第 7 行の）EXTRACT-MIN、そして（第 12 行の）DECREASE-KEY 操作である。&lt;/p&gt;
&lt;p&gt;このアルゴリズムは INSERT と EXTRACT-MIN を各頂点 u ∈ V に対して、1 回ずつ呼び出す。
各頂点 $u ∈ V$ は $S$ にちょうど 1 回だけ挿入されるので、隣接リスト $Adj[u]$ の各辺は、アルゴリズムの実行全体を通して 1 回だけ第 9～12 行の for ループにおいて調べられる。
すべての隣接リストに属する総辺数は $|E|$ であり、for ループの繰返し総数は $|E|$ であり、$DECREASE-KEY$ 操作の総数も全体で高々 $|E|$ 回である。（集計法を使って確認せよ。）&lt;/p&gt;
&lt;p&gt;Prim のアルゴリズムと同様に、Dijkstra のアルゴリズムの実行時間は、min 優先度つきキュー Q の特定の実装方法に依っている。
単に配列の順序を保持していることを利用する：単に配列の $u$ 番目の位置 $v$ を格納するのである。
INSERT と DECREASE-KEY 操作は、それぞれ $O(1)$ 時間で実行できるが、EXTRACT-MIN 操作は（配列全体を調べる必要があるので）$O(V)$ 時間が必要である。
このため、全体で &lt;strong&gt;$O(V^2 + E) = O(V^2)$&lt;/strong&gt; 時間を要す。&lt;/p&gt;
&lt;h2&gt;全点対最短路&lt;/h2&gt;
&lt;h3&gt;Floyd-Warshallアルゴリズム&lt;/h3&gt;
&lt;h4&gt;擬似コード&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;FLOYD-WARSHALL(W, n)
    D(0) = W
    for k = 1 to n
        D(k) = (d[i, j](k))は新しい n x n 型行列である
        for i = 1 to n
            for j = 1 to n
             d[i, j](k) = min{ d[i, j](k - 1), d[i, k](k - 1) + d[k, j](k - 1) }
    return D^(n)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;式(23.7)と式(23.8)に従って行列π(k)を計算するバージョン&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;MOD-FLOYD-WARSHALL(W)
    n = W.rows
    D(0) = W
    let π(0) be a new n × n matrix
    for i = 1 to n
        for j = 1 to n
            if i != j and D[i, j](0) &amp;lt; ∞
                π[i, j](0) = i
    for k = 1 to n
        let D(k) be a new n × n matrix
        let π(k) be a new n × n matrix
        for i = 1 to n
            for j = 1 to n
                if d[i, j](k - 1) ≤ d[i, k](k - 1) + d[k, j](k - 1)
                    d[i, j](k) = d[i, j](k - 1)
                    π[i, j](k) = π[i, j](k - 1)
                else
                    d[i, j](k) = d[i, k](k - 1) + d[k, j](k - 1)
                    π[i, j](k) = π[k, j](k - 1)
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;解析&lt;/h4&gt;
&lt;p&gt;Floyd-Warshallアルゴリズムの実行時間は第2～6行の3重のforループによって定まる。&lt;/p&gt;
&lt;p&gt;第6行は$O(1)$時間で実行できるので、このアルゴリズムの実行時間は$Θ(n³)$である。第23.1節の最後のアルゴリズムと同様、この擬似コードはタイトで、複雑なデータ構造を含まず、$Θ-$記法に隠された定数は小さい。&lt;/p&gt;
&lt;p&gt;したがって、Floyd-Warshallアルゴリズムは、適度に大きな入力グラフに対しても非常に実用的である。&lt;/p&gt;
&lt;h3&gt;疎グラフに対するJohnsonのアルゴリズム&lt;/h3&gt;
&lt;h4&gt;擬似コード&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;JOHNSON(G, w)
    G`を計算する。ここで、G`.V = G`.V ∪ {s}, G`.E = G.E ∪ {(s, v): v ∈ G.V}, かつすべての v ∈ G.V に対して w(s, v) = 0
    if BELLMAN-FORD(G`, w, s) == FALSE
        &quot;入力グラフは負閉路を含む&quot;をプリントする
    else for 各頂点 v ∈ G`.V
            h(v)を BELLMAN-FORD アルゴリズムを用いて計算した δ(s, v) 値に設定
        for 各辺(u, v) ∈ G`.E
            w^(u, v) = w(u, v) + h(u) − h(v)
        D = (d_uv)を新しい n x n 型行列とする
        for 各頂点 u ∈ G.V
            DIJKSTRA(G, w^, u)を実行し、すべての頂点v ∈ G.V に対して δ^(u, v) を計算する
        for 各頂点 v ∈ G.V
            d_uv = δ^(u, v) + h(v) − h(u)
    return D
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;解析&lt;/h4&gt;
&lt;p&gt;Dijkstraのアルゴリズムが用いるmin優先度つきキューをフィボナッチヒープで実装すると、Johnsonのアルゴリズムの実行時間は $O(V²lgV + VE)$ である。
もっと簡単な2分minヒープで実装すると、実行時間は $O(VElgV)$ となるが、グラフが疎ならば、これでもまだFloyd-Warshallのアルゴリズムよりも速い。&lt;/p&gt;
&lt;h2&gt;最大フロー&lt;/h2&gt;
&lt;h3&gt;Ford-Fulkerson法&lt;/h3&gt;
&lt;h4&gt;擬似コード&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;FORD-FULKSON(G, s, t)
    for 各辺 (u, v) ∈ G.E
        (u, v).f = 0
    while 残余ネットワーク Gf に s から t への経路pが存在する
        cf(p) = min{ cf(u, v) : (u, v)はpに属する }
        for 経路 p 上の各辺 (u, v)
            if (u, v) ∈ G.E
                (u, v).f = (u, v).f + cf(p)
            else (v, u).f = (v, u).f - cf(p)
    return f
&lt;/code&gt;&lt;/pre&gt;
</content:encoded></item><item><title>ソートアルゴリズム</title><link>https://www.shiinayane.com/ja/posts/sorting-algorithms/</link><guid isPermaLink="true">https://www.shiinayane.com/ja/posts/sorting-algorithms/</guid><pubDate>Fri, 21 Feb 2025 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;挿入ソート&lt;/h2&gt;
&lt;h3&gt;擬似コード&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;INSERTION-SORT(A, n)
    for i = 2 to n
        key = A[i]
        // A[i]をソート済みの部分配列A[1 : i - 1]に挿入する
        j = i - 1
        while j &amp;gt; 0 and A[j] &amp;gt; key
            A[j + 1] = A[j]
            j = j - 1
        A[j + 1] = key
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;以下は単調減少順のINSERTION-SORT&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;INSERTION-SORT(A, n)
    for i = 2 to n
        key = A[i]
        j = i - 1
        while j &amp;gt; 0 and A[j] &amp;lt; key
            A[j + 1] = A[j]
            j = j - 1
        A[j + 1] = key
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;ループ不変式&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;初期条件&lt;/strong&gt;:&lt;br /&gt;
ループ不変式がループの最初の繰返し（$i = 2$ の繰返し）の直前で成立していることを示すことから始める。&lt;br /&gt;
このとき、部分配列 $A[1:i-1]$ は唯一の要素 $A[1]$ から成され、実際、元 $A[1]$ は格納されていた要素と等しいので、この部分配列はトリビアルにソート済みである。&lt;br /&gt;
よって、最初の繰返しの直前でループ不変式は真である。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;ループ内条件&lt;/strong&gt;:&lt;br /&gt;
ループ不変式をすべての繰返しの直前で真であることを示すため、&lt;br /&gt;
1回の繰返しがループ不変式を維持することを確認する。&lt;br /&gt;
for ループの本体が行っているのは、$A[i]$ を入れるべき場所が見つかるまで $A[i-1]$, $A[i-2], A[i-3]$ をそれぞれ 1 つ右に移し（第 4〜7 行）、
空いた場所 $A[i]$ に値を挿入する（第 8 行）。&lt;br /&gt;
この結果、部分配列 $A[1:i]$ は元 $A[1:i]$ に格納されていた要素から構成されているが、すでにソートされている。&lt;br /&gt;
for ループの次の繰返しのために $i$ の値を 1 増やす（incrementing）とループ不変式が維持される。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;終了条件&lt;/strong&gt;:&lt;br /&gt;
最後に、ループの停止を調べる。ループ変数 $i$ は初期値が 2 で、各繰返しで 1 ずつ増加する。&lt;br /&gt;
第 1 行での値が $n+1$ を超えれば、ループは停止する。つまり、$i = n+1$ で停止し、部分配列 $A[1:n]$ は、開始時点で $A[1:n]$ に格納されていた要素がすべて含まれている。&lt;br /&gt;
これらの要素はすでにソートされている。したがって、アルゴリズムは正当である。&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;解析&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;最悪の場合の定義&lt;/strong&gt;
入力配列が降順に並んでいる場合、各要素を正しい位置に挿入するために、それ以前のすべての要素と比較する必要があります。このとき、最悪実行時間が生じます。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;実行時間の解析&lt;/strong&gt;
挿入ソートの実行時間 $T(n)$ は、以下のループの各行のコストに基づいて計算されます。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;外側の for ループ&lt;/strong&gt; (2行目から8行目):
$i = 2, 3, \ldots, n$ に対して実行されます。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;内側の while ループ&lt;/strong&gt; (5行目から7行目):
最悪の場合、while ループは $A[i]$ が配列 $A[1:i-1]$ のすべての要素より小さい場合に実行され、比較と移動が $i-1$ 回行われます。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;実行時間の式&lt;/strong&gt;
各行のコストを $c_1, c_2, \ldots, c_8$ とした場合、最悪の場合の実行時間は次のように表されます。&lt;/p&gt;
&lt;p&gt;$$
T(n) = c_1 n + c_2 (n-1) + c_4 (n-1) + c_5 \sum_{i=2}^n (i-1) + c_6 \sum_{i=2}^n (i-1) + c_7 \sum_{i=2}^n (i-1) + c_8 (n-1)
$$&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;和の計算&lt;/strong&gt;
以下の和を計算します。&lt;/p&gt;
&lt;p&gt;$\sum_{i=2}^n (i-1)$ を計算：
$$
\sum_{i=2}^n (i-1) = \sum_{i=2}^n i - \sum_{i=2}^n 1 = \left(\sum_{i=1}^n i\right) - 1 - (n-1)
$$&lt;/p&gt;
&lt;p&gt;式 $\sum_{i=1}^n i = \frac{n(n+1)}{2}$ を用いると：
$$
\sum_{i=2}^n (i-1) = \frac{n(n+1)}{2} - n = \frac{n(n-1)}{2}
$$&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;実行時間の整理&lt;/strong&gt;
これを $T(n)$ に代入すると：&lt;/p&gt;
&lt;p&gt;$$
T(n) = c_1 n + c_2 (n-1) + c_4 (n-1) + c_5 \frac{n(n-1)}{2} + c_6 \frac{n(n-1)}{2} + c_7 \frac{n(n-1)}{2} + c_8 (n-1)
$$&lt;/p&gt;
&lt;p&gt;さらにまとめると：
$$
T(n) = \left(\frac{c_5 + c_6 + c_7}{2}\right)n^2 + \left(c_1 + c_2 + c_4 + \frac{c_5 + c_6 + c_7}{2} + c_8\right)n - (c_2 + c_4 + c_5 + c_8)
$$&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;線形関数としての表現&lt;/strong&gt;
最悪の場合の実行時間は、以下のように表されます：&lt;/p&gt;
&lt;p&gt;$$
T(n) = an^2 + bn + c
$$&lt;/p&gt;
&lt;p&gt;ここで：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;$a = \frac{c_5 + c_6 + c_7}{2}$&lt;/li&gt;
&lt;li&gt;$b = c_1 + c_2 + c_4 + \frac{c_5 + c_6 + c_7}{2} + c_8$&lt;/li&gt;
&lt;li&gt;$c = -(c_2 + c_4 + c_5 + c_8)$&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;以上が、挿入ソートの最悪実行時間を導く過程です。この計算により、挿入ソートは最悪の場合において $O(n^2)$ の計算量であることが示されます。&lt;/p&gt;
&lt;h2&gt;線形探索&lt;/h2&gt;
&lt;h3&gt;擬似コード&lt;/h3&gt;
&lt;p&gt;以下は線形探索である&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;LINEAR-SEARCH(A, v)
    for i = 1 to A.length
       if A[i] == v
            return i
    return NIL
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;以下は2分探索である&lt;/p&gt;
&lt;p&gt;反復:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;ITERATIVE-BINARY-SEARCH(A, v, low, high)
    while low ≤ high
        mid = floor((low + high) / 2)
        if v == A[mid]
            return mid
        else if v &amp;gt; A[mid]
            low = mid + 1
        else high = mid - 1
    return NIL
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;再帰:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;RECURSIVE-BINARY-SEARCH(A, v, low, high)
    if low &amp;gt; high
        return NIL
    mid = floor((low + high) / 2)
    if v == A[mid]
        return mid
    else if v &amp;gt; A[mid]
        return RECURSIVE-BINARY-SEARCH(A, v, mid + 1, high)
    else return RECURSIVE-BINARY-SEARCH(A, v, low, mid - 1)
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;選択ソート&lt;/h2&gt;
&lt;h3&gt;擬似コード&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;SELECTION-SORT(A, n)
    n = A.length
    for i = 1 to n - 1
        minIndex = i
        for j = i + 1 to n
            if A[j] &amp;lt; A[minIndex]
                minIndex = j
        A[i]をA[minIndex]と交換する
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;最悪実行時間は$O(n^2)$である。&lt;/p&gt;
&lt;h2&gt;マージソート&lt;/h2&gt;
&lt;h3&gt;擬似コード&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;MERGE(A, p, q, r)
    nL = q - p + 1      // A[p : q]の長さ
    nR = r - q      // A[q + 1 : r]の長さ
    L[0 : nL - 1]とR[0 : nR - 1]を新しい配列とする
    for i = 0 to nL - 1     // A[p : q]をL[0 : nL - 1]にコピーする
        L[i] = A[p + i]
    for j = 0 to nR - 1     // A[q + 1 : r]をR[0 : nR - 1]にコピーする
        R[j] = A[q + j + 1]
    i = 0   // iはLの中で最小の残っている要素のインデックスを登録する
    j = 0   // jはRの中で最小の残っている要素のインデックスを登録する
    k = p   // kはAを埋める場所のインデックスを登録する
    //各配列LとRがまだマージされていない要素を含む限り、まだマージされていない最小の要素をA[p : r]にコピーする
    while i &amp;lt; nL and j &amp;lt; nR
        if L[i] &amp;lt;= R[j]
            A[k] = L[i]
            i = i + 1
        else A[k] = R[j]
            j = j + 1
        k = k + 1
    // LかRの1つを完全に処理したので、もう1つの残りをA[p : r]の最後にコピーする
    while i &amp;lt; nL
        A[k] = L[i]
        i = i + 1
        k = k + 1
    while j &amp;lt; nR
        A[k] = R[j]
        j = j + 1
        k = k + 1
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;MERGE-SORT(A, p, r)
    if p &amp;gt;= r   // 0個か1個の要素？
        return
    q = floor((p + r)/2)    // A[p : r]の中点
    MERGE-SORT(A, p, q)   // 再帰的にA[p : q]をソート
    MERGE-SORT(A, q + 1, r)   // 再帰的にA[q + 1 : r]をソート
    // A[p : q]とA[q + 1 : r]をマージしてA[p : r]へ戻す
    MERGE(A, p, q, r)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;以下はセンチネルを使用せずのMERGE&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;MERGE(A, p, q, r)
    n1 = q - p + 1
    n2 = r - q
    L[0 : n1 - 1]とR[0 : n1 - 1]を新しい配列とする
    for i = 1 to n1
        L[i] = A[p + i - 1]
    for j = 1 to n2
        R[j] = A[q + j]
    i = 1
    j = 1
    for k = p to r
        if i &amp;gt; n1
            A[k] = R[j]
            j = j + 1
        else if j &amp;gt; n2
            A[k] = L[i]
            i = i + 1
        else if L[i] ≤ R[j]
            A[k] = L[i]
            i = i + 1
        else
            A[k] = R[j]
            j = j + 1
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;解析&lt;/h3&gt;
&lt;p&gt;以下は、マージソート(Merge Sort)の最悪実行時間について、簡単に説明します。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;マージソートの基本動作&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;分割(Divide)&lt;/strong&gt;&lt;br /&gt;
配列を半分に分割し、各部分を再帰的にソートします。この操作は配列サイズが $n$ の場合、分割の深さが $\log n$ レベルになります。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;統治(Conquer)&lt;/strong&gt;&lt;br /&gt;
各部分配列を結合（マージ）してソート済みの配列を作成します。各レベルで配列全体にわたってマージ操作を行うので、各レベルのコストは $O(n)$ です。&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;再帰的な式の構築&lt;/strong&gt;
分割と結合を考慮すると、マージソートの実行時間 $T(n)$ は以下のような再帰式で表されます：&lt;/p&gt;
&lt;p&gt;$$
T(n) = 2T\left(\frac{n}{2}\right) + cn
$$&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;$2T\left(\frac{n}{2}\right)$：配列を2つに分割し、それぞれの部分配列をソートする時間。&lt;/li&gt;
&lt;li&gt;$cn$：現在のレベルでのマージ操作に必要な時間。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;再帰式の解法&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;再帰式を展開すると、分割の深さごとにコストを計算できます。&lt;/li&gt;
&lt;li&gt;再帰の深さは $\log n$ レベルであり、各レベルのコストは $O(n)$ です。&lt;/li&gt;
&lt;li&gt;すべてのレベルのコストを合計すると：&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;$$
T(n) = cn \log n + cn
$$&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;最悪実行時間の結論&lt;/strong&gt;
最悪実行時間は再帰的分割と統治に基づき、次のように表されます：&lt;/p&gt;
&lt;p&gt;$$
T(n) = \Theta(n \log n)
$$&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;バブルソート&lt;/h2&gt;
&lt;h3&gt;擬似コード&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;BUBBLESORT(A, n)
    for i = 1 to n - 1
        for j = n downto i + 1
            if A[j] &amp;lt; A[j - 1]
                A[j]とA[j - 1]を交換する
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;ループ不変式&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;行2 ~ 4のforループ
行2-4の for ループの各繰り返しの開始時点で、部分配列 $A[j..n]$ は、ループ開始前に $A[j..n]$ に存在していた要素から構成されますが、その順序は変わっている可能性があります。
ただし、先頭の要素 $A[j]$ はその部分配列内で最小の要素です。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;初期条件&lt;/strong&gt;:&lt;br /&gt;
初期状態では、部分配列 $A[n]$ は最後の要素のみを含んでおり、この要素は自明的に部分配列内の最小要素です。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;ループ内条件&lt;/strong&gt;:&lt;br /&gt;
各ステップで $A[j]$ と $A[j-1]$ を比較し、$A[j-1]$ をその部分配列内で最小の要素にします。ループの繰り返し後、部分配列の長さは1つ増加し、先頭の要素は依然としてその部分配列内で最小の要素です。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;終了条件&lt;/strong&gt;:&lt;br /&gt;
ループは $j = i$ のとき終了します。ループ不変式によると、$A[i]$ は $A[i..n]$ 内で最小の要素であり、$A[i..n]$ はループ開始前の $A[i..n]$ に存在していた要素から構成されています。&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;行1 ~ 4のforループ
行1-4の for ループの各繰り返しの開始時点で、部分配列 $A[1..i-1]$ は、$A[1..n]$ 内の $i-1$ 個の最小要素で構成され、昇順に整列されています。
一方、$A[i..n]$ は $A[1..n]$ 内の残りの $n-i+1$ 個の要素で構成されています。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;初期条件&lt;/strong&gt;:&lt;br /&gt;
初期状態では、部分配列 $A[1..i-1]$ は空であり、自明的に部分配列内の最小要素が含まれています。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;ループ内条件&lt;/strong&gt;:&lt;br /&gt;
(b) の結果から、内側のループの実行後、$A[i]$ は部分配列 $A[i..n]$ 内で最小の要素になります。そして外側のループ開始時点では、$A[1..i-1]$ は $A[i..n]$ の要素より小さい要素から構成され、昇順に整列されています。
したがって、外側のループの実行後、部分配列 $A[1..i]$ は $A[i+1..n]$ の要素より小さい要素から構成され、昇順に整列されます。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;終了条件&lt;/strong&gt;:&lt;br /&gt;
ループは $i = A.length$ のとき終了します。この時点で配列 $A[1..n]$ はすべての要素を含み、昇順に整列されています。&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;ヒープソート&lt;/h2&gt;
&lt;h3&gt;ヒープ条件の維持&lt;/h3&gt;
&lt;h4&gt;擬似コード&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;MAX-HEAPIFY(A, i)
    l = LEFT(i)
    r = RIGHT(i)
    if l &amp;lt;= heap-size[A] and A[l] &amp;gt; A[i]
        largest = l
    else largest = i
    if r &amp;lt;= heap-size[A] and A[r] &amp;gt; A[largest]
        largest = r
    if largest != i
        A[i]をA[largest]と交換する
        MAX-HEAPIFY(A, largest)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;MIN-HEAPIFYバージョン&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;MIN-HEAPIFY(A, i)
    l = LEFT(i)
    r = RIGHT(i)
    if l ≤ A.heap-size and A[l] &amp;lt; A[i]
        smallest = l
    else smallest = i
    if r ≤ A.heap-size and A[r] &amp;lt; A[smallest]
        smallest = r
    if smallest != i
        A[i]をA[smallest]と交換する
        MIN-HEAPIFY(A, smallest)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;再帰の代わりに繰り返し構造子 (ループ) を使うバージョン&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;MAX-HEAPIFY(A, i)
    while true
        l = LEFT(i)
        r = RIGHT(i)
        if l ≤ A.heap-size and A[l] &amp;gt; A[i]
            largest = l
        else largest = i
        if r ≤ A.heap-size and A[r] &amp;gt; A[largest]
            largest = r
        if largest == i
            return
        A[i]をA[largest]と交換する
        i = largest
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;解析&lt;/h4&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;操作の流れ&lt;/strong&gt;
MAX-HEAPIFYは、与えられた節点 $i$ を根とする部分木が max ヒープ条件を満たすように調整する操作です。この操作の流れは以下の通りです：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;比較と選択&lt;/strong&gt;:&lt;br /&gt;
節点 $i$ の値をその左右の子（$LEFT(i)$ および $RIGHT(i)$）の値と比較し、3つの中で最大の値を持つ節点を特定します。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;交換と再帰呼び出し&lt;/strong&gt;:&lt;br /&gt;
最大値を持つ節点が $i$ ではない場合、節点 $i$ とその最大値を持つ子を交換します。その後、交換によって影響を受けた子部分木に対して再帰的に &lt;strong&gt;MAX-HEAPIFY&lt;/strong&gt; を呼び出します。&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;実行時間の解析&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;1回の呼び出しのコスト&lt;/strong&gt;:&lt;br /&gt;
1回の比較操作(左右の子と比較)にかかる時間は定数時間 $O(1)$ です。ただし、交換後に再帰的に操作を繰り返すため、実行時間は部分木の高さに依存します。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;再帰関係&lt;/strong&gt;:&lt;br /&gt;
再帰的な呼び出しに基づく実行時間は以下の漸化式で表されます：
$$
T(n) \leq T\left(\frac{2n}{3}\right) + O(1)
$$
ここで、$\frac{2n}{3}$ は、節点 $i$ の2つの子のうち、より大きな部分木のサイズを示します。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;解法と結論&lt;/strong&gt;:&lt;br /&gt;
マスター定理（画像の参照）を適用すると、この漸化式の解は：
$$
T(n) = O(\log n)
$$
となります。したがって、&lt;strong&gt;MAX-HEAPIFY&lt;/strong&gt; の最悪実行時間は木の高さ（$\log n$）に比例します。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;ヒープの構築&lt;/h3&gt;
&lt;h4&gt;擬似コード&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;BUILD-MAX-HEAP(A, n)
    A.heap-size = n
    for i = floor(n/2) downto 1
        MAX-HEAPIFY(A, i)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;MAX-HEAP-INSERTを呼び出しバージョン&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;BUILD-MAX-HEAP`(A)
    A.heap-size = 1
    for i = 2 to A.length
        MAX-HEAP-INSERT(A, A[i])
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;解析&lt;/h4&gt;
&lt;h5&gt;ループ不変式&lt;/h5&gt;
&lt;p&gt;ループ不変式の定義:&lt;br /&gt;
for ループ(行2 - 3)の各繰り返しの開始時点で、各節点 $i+1, i+2, \ldots, n$ は max ヒープの根である。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;初期条件&lt;/strong&gt;:&lt;br /&gt;
ループの最初の繰り返しの直前では、$i = \lfloor n/2 \rfloor$ である。&lt;br /&gt;
このとき、$i+1, i+2, \ldots, n$ は葉ノードであり、葉ノードは自明的に max ヒープの根である。したがって、ループ不変式は成立する。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;ループ内条件&lt;/strong&gt;:&lt;br /&gt;
各繰り返しで、節点 $i$ の左右の子を根とする部分木に対して &lt;strong&gt;MAX-HEAPIFY&lt;/strong&gt; を呼び出す。この操作により、節点 $i$ を根とする部分木が max ヒープ条件を満たすようになる。&lt;br /&gt;
さらに、次の繰り返しでは $i$ が 1 減少するため、ループ不変式は維持される。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;終了条件&lt;/strong&gt;:&lt;br /&gt;
手続きは $i = 0$ で終了する。ループ不変式によれば、終了時点で各節点 $1, 2, \ldots, n$ が max ヒープの根である。したがって、配列全体が max ヒープになっている。&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h5&gt;実行時間&lt;/h5&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;MAX-HEAPIFY のコスト&lt;/strong&gt;:&lt;br /&gt;
MAX-HEAPIFY の実行時間は節点の高さに比例し、最悪の場合 $O(h)$ です。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;控える必要な事実&lt;/strong&gt;:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;高さ $h$ の節点数はおおよそ $\lceil n/2^{h+1} \rceil$ 個です。&lt;/li&gt;
&lt;li&gt;n個節点を含むヒープの深さはおおよそ $\lfloor \log n \rfloor$ である。&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;全体のコスト&lt;/strong&gt;:&lt;br /&gt;
BUILD-MAX-HEAP のコストは、すべての節点に対して MAX-HEAPIFY を適用する際の時間の合計です。この合計は以下のように表されます：
$$
\begin{aligned}
T(n)
&amp;amp;= \sum_{h=0}^{\lfloor \log n \rfloor} \left\lceil \frac{n}{2^{h+1}} \right\rceil O(h) \
&amp;amp;\leq \sum_{h=0}^{\lfloor \log n \rfloor} \frac{n}{2^h} ch \
&amp;amp;= cn \sum_{h=0}^{\lfloor \log n \rfloor} \frac{h}{2^h} \
&amp;amp;&amp;lt; cn \sum_{h=0}^{\infty} \frac{h}{2^h}
\end{aligned}
$$
公式$\sum_{k=0}^{\infty} kx^k = \frac{x}{(1-x)^2}$を用いると:&lt;br /&gt;
$$
T(n) \leq cn \frac{\frac{1}{2}}{\left(1-\frac{1}{2}\right)^2} = O(n)
$$&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;漸近的評価&lt;/strong&gt;:&lt;br /&gt;
付録の公式（図中の式）を用いると、この和は定数に収束することが示されます。したがって：
$$
T(n) = O(n)
$$&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;ヒープソートアルゴリズム&lt;/h3&gt;
&lt;h4&gt;擬似コード&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;HEAPSORT(A, n)
    BUILD-MAX-HEAP(A, n)
    for i = n downto 2
        A[1]をA[i]と交換する
        A.heap-size = A.heap-size - 1
        MAX-HEAPIFY(A, 1)
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;解析&lt;/h4&gt;
&lt;h5&gt;ループ不変式&lt;/h5&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;初期条件&lt;/strong&gt;:&lt;br /&gt;
部分配列 $A[i+1..n]$ は空であるため、この時点でループ不変式は成立している。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;維持条件&lt;/strong&gt;:&lt;br /&gt;
$A[1]$ は部分配列 $A[1..i]$ の中で最大の要素であり、$A[i+1..n]$ の要素よりも小さい。&lt;br /&gt;
この要素を位置 $i$ に移動させることで、部分配列 $A[i..n]$ は最大の要素を含み、整列されている。&lt;br /&gt;
ヒープのサイズを減らし、&lt;strong&gt;MAX-HEAPIFY&lt;/strong&gt; を呼び出すことで、部分配列 $A[1..i-1]$ が再び max ヒープになる。&lt;br /&gt;
その後、$i$ をデクリメントすることで、次の繰り返しに対するループ不変式が整備される。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;終了条件&lt;/strong&gt;:&lt;br /&gt;
ループが終了するとき、$i = 1$ である。&lt;br /&gt;
この時点で、部分配列 $A[2..n]$ は整列済みであり、$A[1]$ は配列内で最小の要素である。&lt;br /&gt;
したがって、配列全体 $A[1..n]$ は整列されている。&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h5&gt;実行時間&lt;/h5&gt;
&lt;p&gt;BUILD-MAX-HEAPの呼出しに$O(n)$時間かかり、1回の呼出しに$O(\log n)$時間かかるMAX-HEAPIFYが$n - 1$回呼び出されるので、手続きHEAPSORTの実行時間は$O(n \log n)$である。&lt;/p&gt;
&lt;h2&gt;クイックソート&lt;/h2&gt;
&lt;h3&gt;擬似コード&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;QUICKSORT(A, p, r)
    if p &amp;lt; r
        // ピボットを中心に部分配列を分割する。ピボットは最終的にA[q]で終わる
        q = PARTITION(A, p, r)
        QUICKSORT(A, p, q - 1)  // 下側を再帰的にソート
        QUICKSORT(A, q + 1, r)  // 上側を再帰的にソート
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;PARTITION(A, p, r)
    x = A[r]    // ピボット
    i = p - 1   // 下側で最大のインデックス
    for j = p to r - 1  // ピボット以外の各要素を処理
        if A[j] &amp;lt;= x    // この要素は下側に属する？
            i = i + 1   // 下側の新しいスロットのインデックス
            A[i]をA[j]と交換する    // この要素をそこに置く
    A[i + 1]をA[r]と交換する    // ピボットは下側のすぐ右に移動
    return i + 1    // ピボットの新しいインデックス
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;以下はHOAREの最初のPARTITIONアルゴリズム&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;HOARE-PARTITION(A, p, r)
    x = A[p]
    i = p - 1
    j = r + 1
    while TRUE
        repeat
            j = j - 1
        until A[j] &amp;lt;= x
        repeat
            x = x + 1
        until A[x] &amp;gt;= x
        if x &amp;lt; y
            A[i]をA[j]と交換する
        else return j
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;乱択版擬似コード&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;RANDOMIZED-QUICKSORT(A, p, r)
    if p &amp;lt; r
        q = RANDOMIZED-PARTITION(A, p, r)
        QUICKSORT(A, p, q - 1)
        QUICKSORT(A, q + 1, r)
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;RANDOMIZED-PARTITION(A, p, r)
    i = RANDOM(p, r)
    A[r]をA[i]と交換する
    return PARTITION(A, p, r)
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;解析&lt;/h3&gt;
&lt;h4&gt;ループ不変式&lt;/h4&gt;
&lt;p&gt;行 3 - 4 の各繰り返しの開始時点で、部分配列 $A[p..i]$ は $x$ 以下の値を含み、$A[i+1..j-1]$ は $x$ より大きい値を含む。
$A[j..r-1]$ は未確認の値で構成され、$A[r]$ はピボット値 $x$ である。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;初期条件&lt;/strong&gt;
ループの最初の繰り返しの直前では、$i = p-1$ かつ $j = p$ である。このとき、部分配列 $A[i+1..j-1]$ と $A[j..r-1]$ は空であるため、ループ不変式が成立する。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;維持条件&lt;/strong&gt;
各繰り返しで、次の 2 つの場合を考える：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;$A[j] &amp;gt; x$ の場合&lt;/strong&gt;:&lt;br /&gt;
この場合、$j$ を単に $1$ 増加させる。これにより、$A[j]$ は $A[i+1..j-1]$ に加えられることなく $x$ より大きい部分に残り、ループ不変式が維持される。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;$A[j] \leq x$ の場合&lt;/strong&gt;:&lt;br /&gt;
この場合、$i$ を $1$ 増加させた後、$A[i]$ と $A[j]$ を交換する。この操作により、$A[i]$ は $A[p..i]$ に追加され、$x$ 以下の値として正しい位置に収まる。結果としてループ不変式が維持される。&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;終了条件&lt;/strong&gt;
ループは $j = r$ で停止する。この時点で、$A[j..r-1]$ は空となり、配列全体は次の 3 つの部分に分割されている：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;$A[p..i]$：$x$ 以下の値。&lt;/li&gt;
&lt;li&gt;$A[i+1..r-1]$：$x$ より大きい値。&lt;/li&gt;
&lt;li&gt;$A[r]$：ピボット値 $x$。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;ピボット値を正しい位置 $i+1$ に移動することで、PARTITION 手続きは完了し、部分配列 $A[p..r]$ が適切に分割される。&lt;/p&gt;
&lt;h4&gt;最悪実行時間&lt;/h4&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;漸化式&lt;/strong&gt;
QUICKSORT の最悪実行時間は、分割が極端に偏った場合（片側にすべての要素が集中するケース）に発生します。この場合、漸化式は次のように表されます：
$$
T(n) = T(q) + T(n-1-q) + \Theta(n), \quad 0 \leq q \leq n-1
$$
ここで、$q$ は部分配列の要素数を示します。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;最悪ケース&lt;/strong&gt;
最悪ケースでは、分割が片側に極端に偏るため、$q = 0$ または $q = n-1$ となります。この場合の漸化式は：
$$
T(n) = T(n-1) + \Theta(n)
$$&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;漸化式の解&lt;/strong&gt;
上記の漸化式を展開すると：&lt;/p&gt;
&lt;p&gt;$$
\begin{aligned}
T(n)
&amp;amp;= T(n-1) + \Theta(n) \
&amp;amp;= T(n-2) + \Theta(n-1) + \Theta(n) \
&amp;amp;= \cdots \
&amp;amp;= T(1) + \sum_{i=1}^{n} \Theta(i)
\end{aligned}
$$&lt;/p&gt;
&lt;p&gt;最後の和は等差数列であるため：&lt;/p&gt;
&lt;p&gt;$$
T(n) = \Theta(n^2)
$$&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h4&gt;期待実行時間&lt;/h4&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;分割の期待ケース&lt;/strong&gt;
RANDOMIZED-QUICKSORT は、ランダムにピボットを選択することで、分割がほぼ均等になるように設計されています。このアルゴリズムの期待実行時間を解析するには、次の漸化式を考えます：
$$
T(n) = \max{T(q) + T(n-1-q)} + \Theta(n), \quad 0 \leq q \leq n-1
$$
ただし、ランダム性を考慮すると、分割が偏りにくいため、$q \approx n/2$ に近いケースが期待されます。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;比較回数の期待値&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;補題: 任意の2つの要素$z_i$と$z_j$ (i &amp;lt; j) が与えられたとき、この2つの要素が比較される確率は$2/(j - i + 1)$である。
証明：
$$
\begin{aligned}
P{z_i \text{ and } z_j \text{ are compared}}
&amp;amp;= P{z_i \text{ or } z_j \text{ is the first pivot chosen from } Z_{ij}} \
&amp;amp;= P{z_i \text{ is the first pivot chosen from } Z_{ij}} \
&amp;amp;\quad + P{z_j \text{ is the first pivot chosen from } Z_{ij}} \
&amp;amp;= \frac{2}{j - i + 1}
\end{aligned}
$$&lt;/li&gt;
&lt;li&gt;比較回数 $X$ をランダム変数として、指標確率変数を $X_{ij} = I{z_i \text{ and } z_j \text{ are compared}}$ と定義する。&lt;/li&gt;
&lt;li&gt;以上より、$X = \sum_{i=1}^{n-1} \sum_{j=i+1}^n X_{ij}$になっている。&lt;/li&gt;
&lt;li&gt;期待値 $\mathbb{E}[X]$ は次のように評価されます：&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;$$
\begin{aligned}
\mathbb{E}[X]
&amp;amp;= \mathbb{E}\left[\sum_{i=1}^{n-1} \sum_{j=i+1}^n X_{ij}\right] \
&amp;amp;= \sum_{i=1}^{n-1} \sum_{j=i+1}^n \mathbb{E}[X_{ij}] \
&amp;amp;= \sum_{i=1}^{n-1} \sum_{j=i+1}^n \mathbb{E}[I{z_i \text{ and } z_j \text{ are compared}}]
\end{aligned}
$$&lt;/p&gt;
&lt;p&gt;補題を用いると：&lt;/p&gt;
&lt;p&gt;$$
\mathbb{E}[X] = \sum_{i=1}^{n-1} \sum_{j=i+1}^n \frac{2}{j-i+1}
$$&lt;/p&gt;
&lt;p&gt;変数を $k = j-i$ に変換し、調和数の公式 $\sum_{k=1}^{n} \frac{1}{k} = \ln n + O(1)$ を用いると：&lt;/p&gt;
&lt;p&gt;$$
\begin{aligned}
\mathbb{E}[X]
&amp;amp;= \sum_{i=1}^{n-1} \sum_{k=1}^{n-i} \frac{2}{k+1} \
&amp;amp;&amp;lt; \sum_{i=1}^{n-1} \sum_{k=1}^{n} \frac{2}{k} \
&amp;amp;= \sum_{i=1}^{n-1} O(\log n) \
&amp;amp;= O(n \log n)
\end{aligned}
$$&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;結論&lt;/strong&gt;
ランダム性によって分割のバランスが期待されるため、RANDOMIZED-QUICKSORT の期待実行時間は次のように評価されます：
$$
T(n) = O(n \log n)
$$&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;計数ソート&lt;/h2&gt;
&lt;h3&gt;擬似コード&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;COUNTING-SORT(A, n, k)
    B[1 : n]とC[0 : k]を新しい配列とする
    for i = 0 to k
        C[i] = 0
    for j = 1 to n
        C[A[j]] = C[A[j]] + 1
    // C[i]はiに等しい要素の個数を持っている
    for i = 1 to k
        C[i] = C[i] + C[i - 1]
    // C[i]はi以下の要素の数を示す
    // AはBにコピーし、Aの最後から始める
    for j = n downto 1
        B[C[A[j]]] = A[j]
        C[A[j]] = C[A[j]] - 1   // 重複する値の扱い
    return B
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;MODIFIED-COUNTING-SORT(A, n, k)
    let C[0..k] be a new array
    for i = 1 to k
        C[i] = 0
    for j = 1 to n
        C[A[j]] = C[A[j]] + 1
    for i = 2 to k
        C[i] = C[i] + C[i - 1]
    insert sentinel element NIL at the start of A
    B = C[0..k - 1]
    insert number 1 at the start of B
    // B now contains the &quot;endpoints&quot; for C
    for i = 2 to n
        while C[A[i]] != B[A[i]]
            key = A[i]
            exchange A[C[A[i]]] with A[i]
            while A[C[key]] == key // make sure that elements with the same keys will not be swapped
                C[key] = C[key] - 1
    remove the sentinel element
    return A
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;解析&lt;/h3&gt;
&lt;h4&gt;実行時間&lt;/h4&gt;
&lt;p&gt;計数ソートの実行時間を検討する。
第2 ~ 3行のforループに$\Theta(k)$時間、第4 ~ 5行のforループに$\Theta(n)$時間、第7 ~ 8行のforループに$\Theta(k)$時間、
そして第11 ~ 13行のforループに$\Theta(n)$時間がかかる。
したがって、全体の実行時間は$\Theta(k + n)$である。通常$k = O(n)$のとき、計数ソートを用いる。実際には実行時間は$\Theta(n)$である。&lt;/p&gt;
&lt;h2&gt;基数ソート&lt;/h2&gt;
&lt;h3&gt;擬似コード&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;RADIX-SORT(A, n, d)
    for i = 1 to d
        安定ソートを用いて第i桁に関して配列A[1 : n]をソートする　
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;解析&lt;/h3&gt;
&lt;h4&gt;補題 8.3&lt;/h4&gt;
&lt;p&gt;$n$ 個の $d$ 桁の数が与えられていて、各桁が取りうる値の数が $k$ 以下であると仮定する。サブルーチンとして用いる安定ソートの実行時間が $\Theta(n + k)$ ならば、Radix-Sort はこれらの数を次の時間でソートする：&lt;/p&gt;
&lt;p&gt;$$
\Theta(d(n + k))
$$&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;証明&lt;/strong&gt;&lt;br /&gt;
Radix-Sort の正当性はソートされている列に関する帰納法によって示すことができる（練習問題 8.3-3 を参照）。&lt;br /&gt;
実行時間の解析は中間ソートとして用いる安定ソートに依存する。&lt;br /&gt;
各桁のうち 0 から $k - 1$ の範囲にあり（それぞれ $k$ 個の値を取りうる）、各桁のソートは $\Theta(n + k)$ 時間を必要とする。&lt;br /&gt;
この操作を $d$ 桁すべてに対して実行するため、全体の実行時間は：&lt;/p&gt;
&lt;p&gt;$$
\Theta(d(n + k))
$$&lt;/p&gt;
&lt;p&gt;となる。&lt;/p&gt;
&lt;h4&gt;補題 8.4&lt;/h4&gt;
&lt;p&gt;$n$ 個の $d$ 桁の数が与えられていて、各桁の数を $b$-ビットで表現する。サブルーチンとして Counting-Sort を使用する場合、Radix-Sort の総実行時間は次の式で与えられる：&lt;/p&gt;
&lt;p&gt;$$
\Theta\left( \frac{b}{r}\left( n + 2^r \right) \right)
$$&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;証明&lt;/strong&gt;&lt;br /&gt;
値を各桁に対して、各キーを $b$-ビットで表現した場合における次のような実行時間を考える。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;各桁あたりの取りうる値の数を $2^r$ とする。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;サブルーチン Counting-Sort の実行時間は $\Theta(n + 2^r)$ であり、これは各桁ごとのコストを決定する。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;全体の桁数 $d$ に基づき、Radix-Sort の実行時間は：&lt;/p&gt;
&lt;p&gt;$$
\Theta(d(n + 2^r))
$$&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;条件 $b = \log n$ を満たすとき、実行時間は $O(n \log n)$ に近くなる。&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;したがって、総実行時間は次の式で表される：&lt;/p&gt;
&lt;p&gt;$$
\Theta\left( \frac{b}{r}\left( n + 2^r \right) \right)
$$&lt;/p&gt;
&lt;h2&gt;バケツソート&lt;/h2&gt;
&lt;h3&gt;擬似コード&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;BUCKET-SORT(A, n)
    B[0 : n - 1]を新しい配列とする
    for i = 0 to n - 1
        B[i]を空リストに初期化する
    for i = 1 to n
        A[i]をリストB[floor(n * A[i])]に挿入する
    for i = 0 to n - 1
        リストB[i]を挿入ソートでソートする
    リストB[0], B[1], ..., B[n - 1]をこの順序で連接する
    return 連結されたリスト
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;解析&lt;/h3&gt;
&lt;h4&gt;実行時間&lt;/h4&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;実行時間の分解&lt;/strong&gt;
バケットソートの実行時間を解析するため、以下の2つの部分に分けて考える：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;第7行以外&lt;/strong&gt;の実行時間は、最悪の場合でも $O(n)$ 時間である。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;第7行の挿入ソート&lt;/strong&gt;にかかる実行時間は、各バケット内の要素数 $n_i$ に依存する。&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;全体の実行時間&lt;/strong&gt;
挿入ソートは 2 次の多項式時間で動作するため、バケットソート全体の実行時間 $T(n)$ は以下の式で表される：
$$
T(n) = \Theta(n) + \sum_{i=0}^{n-1} O(n_i^2)
$$&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h4&gt;平均時の実行時間の期待値&lt;/h4&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;期待値の計算&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;期待値を計算する際、入力分布の一様性に基づき、期待値の線形性を適用する：
$$
\begin{aligned}
E[T(n)]
&amp;amp;= E\left[\Theta(n) + \sum_{i=0}^{n-1} O(n_i^2)\right] \
&amp;amp;= \Theta(n) + \sum_{i=0}^{n-1} E[O(n_i^2)] \
&amp;amp;= \Theta(n) + n \cdot O(E[n_i^2])
\end{aligned}
$$&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;各バケットの要素数 $n_i^2$ の期待値&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;各バケットに要素が均等に割り振られると仮定すると：
$$
E[n_i^2] = Var[n_i] + E^2[n_i]= (1 - 1/n) + 1^2 = 2 - 1/n
$$&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h4&gt;結論&lt;/h4&gt;
&lt;p&gt;バケットソートの期待実行時間は以下のように評価される：
$$
E[T(n)] = \Theta(n) + n \cdot O(2 - 1/n) = \Theta(n)
$$&lt;/p&gt;
&lt;h2&gt;線形期待時間選択アルゴリズム&lt;/h2&gt;
&lt;h3&gt;擬似コード&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;RANDOMIZED-SELECT(A, p, r, i)
    if p == r
        return A[p] // 1 ≤ i ≤ r − p + 1 は p == r が i = 1 である。
    q = RANDOMIZED-PARTITION(A, p, r)
    k = q − p + 1
    if i == k
        return A[q] // このピボットの値が答えである
    elseif i &amp;lt; k
        return RANDOMIZED-SELECT(A, p, q − 1, i)
    else return RANDOMIZED-SELECT(A, q + 1, r, i − k)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;以下は反復バージョン&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;PARTITION(A, p, r)
    x = A[r]
    i = p
    for k = p - 1 to r
       if A[k] &amp;lt; x
           i = i + 1
           swap A[i] with A[k]
    i = i + 1
    swap A[i] with A[r]
    return i
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;RANDOMIZED-PARTITION(A, p, r)
    x = RANDOM(p - 1, r)
    swap A[x] with A[r]
    return PARTITION(A, p, r)
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;RANDOMIZED-SELECT(A, p, r, i)
    while true
        if p == r
            return A[p]
        q = RANDOMIZED-PARTITION(A, p, r)
        k = q - p + 1
        if i == k
            return A[q]
        if i &amp;lt; k
            r = q - 1
        else
            p = q + 1
            i = i - k
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;解析&lt;/h3&gt;
&lt;h4&gt;定理 9.2&lt;/h4&gt;
&lt;p&gt;$n$ 個の異なる要素からなる入力配列に対する手続き &lt;strong&gt;RANDOMIZED-SELECT&lt;/strong&gt; の期待実行時間は以下である：
$$
\Theta(n)
$$&lt;/p&gt;
&lt;h4&gt;証明&lt;/h4&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;分割が有用である確率の定義&lt;/strong&gt;
有用な分割とは、分割が進むたびに要素の集合が十分に減少する分割を指す。各分割について次のように定義する：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;集合のサイズ&lt;/strong&gt;：$A(h_k)$（世代 $k$ における集合）。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;次の分割後の集合サイズ&lt;/strong&gt;：$A(h_k + 1), A(h_k + 2), ..., A(h_k + X_k - 1)$。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;ここで：
$$
X_k = h_{k+1} - h_k
$$
を定義する。つまり、$X_k$ は世代 $k$ における分割後の集合の個数を表す。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;分割の有効性と確率&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;分割が有用である確率は少なくとも 1/2 である。&lt;/li&gt;
&lt;li&gt;このとき、分割のたびに集合のサイズは $(3/4) n_0$ 以下に縮小する（$n_0$ は元の入力配列サイズ）。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;期待値の計算&lt;/strong&gt;
分割が有効である場合に、比較回数の期待値を計算する：&lt;/p&gt;
&lt;p&gt;$$
E[T(n)] = E\left[\sum_{k=0}^{m-1} X_k \cdot \left(\frac{3}{4}\right)^k n_0\right]
$$&lt;/p&gt;
&lt;p&gt;ここで $E[X_k] \leq 2$ であることから：&lt;/p&gt;
&lt;p&gt;$$
E[T(n)] \leq n_0 \sum_{k=0}^{m-1} \left(\frac{3}{4}\right)^k \cdot 2
$$&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h4&gt;結論&lt;/h4&gt;
&lt;p&gt;等比数列の和を用いて整理すると：&lt;/p&gt;
&lt;p&gt;$$
E[T(n)] \leq 2n_0 \cdot \frac{1}{1 - \frac{3}{4}} = 8n_0
$$&lt;/p&gt;
&lt;p&gt;したがって、RANDOMIZED-SELECT の期待実行時間は次のように評価される：&lt;/p&gt;
&lt;p&gt;$$
\Theta(n)
$$&lt;/p&gt;
&lt;h2&gt;線形最悪時間選択アルゴリズム&lt;/h2&gt;
&lt;h3&gt;擬似コード&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;SELECT(A, p, r, i)
    while (r − p + 1) mod 5 != 0
        for j = p + 1 to r
            if A[p] &amp;gt; A[j] // 最小値を A[p] に置く
                A[p] を A[j] と交換する
        // A[p:r] の最小値が得られれば、これで終わり
        if i == 1
            return A[p]
        // そうでなければ、A[p + 1:r] の (i − 1) 番目の要素を得たい
        p = p + 1
        i = i − 1
    g = (r − p + 1) / 5     // 5 要素のグループの個数
    for j = p to p + g − 1  // 各グループをソート
        &amp;lt;A[j], A[j + g], A[j + 2g], A[j + 3g], A[j + 4g]&amp;gt; をその場でソートする
    // すべてのグループ中央値が、今ではA[p : r]の中央の5番目にある
    x = SELECT(A, p + 2g, p + 3g − 1, ceiling(g/2))
    q = PARTITION-AROUND(A, p, r, x)    // ピボットに関して分割
    // 残りは RANDOMIZED-SELECT の第3 ~ 9行とまったく同じである
    k = q + p + 1
    if i == k
        return A[q]     // ピボットの値が答えである
    elseif i &amp;lt; k
        return SELECT(A, p, q − 1, i)
    else return SELECT(A, q + 1, r, i − k)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;以下は3グループのバージョン&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;SELECT3(A, p, r, i)
    while (r - p + 1) mod 9 != 0
        for j = p + 1 to r      // 最小値を A[p] に置く
            if A[p] &amp;gt; A[j]
                A[p] を A[j] で置き換える
        // A[p : r] の最小値を求めることが目的なら、ここで終了
        if i == 1
            return A[p]
        // そうでなければ、A[p + 1 : r] の (i - 1) 番目の要素がほしい
        p = p + 1
        i = i - 1
    g = (r - p + 1) / 3     // 3 要素のグループの個数
    for j = p to p + g - 1  // グループを通して調べる
        &amp;lt;A[j], A[j + g], A[j + 2g]&amp;gt; をその場でソート
    // すべてのグループの中央値は今や A[p : r] の中央の 3 番目にある
    g^ = g / 3 // 3 要素の副グループの個数
    for j = p to p + g^ - 1     // 副グループをソートする
        &amp;lt;A[j], A[j + g^], A[j + 2g^]&amp;gt; をその場でソートする in place
    // すべての副グループの中央値は、今や A[p : r] の中央の 9 番目にある
    // ピボット x を再帰的に副グループ中央値の中央値として求める
    x = SELECT3(A, p + 4g^, p + 5g^ - 1, ceiling(g^ / 2))
    q = PARTITION-AROUND(A, p, r, x) // ピボットに関して分割
    // 残りは SELECT3 の第19-24行とまったく同じである
    k = q - p + 1
    if i == k
        return A[q] // このピボットの値が答えである
    elseif i &amp;lt; k
        return SELECT3(A, p, q - 1, i)
    else return SELECT3(A, q + 1, r, i - k)
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;解析&lt;/h3&gt;
&lt;h4&gt;定理 9.3&lt;/h4&gt;
&lt;p&gt;長さ $n$ の入力配列に関する &lt;strong&gt;SELECT&lt;/strong&gt; の実行時間は $\Theta(n)$ である。&lt;/p&gt;
&lt;h4&gt;証明&lt;/h4&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;概要&lt;/strong&gt;
手続き SELECT を用いて、長さ $n$ の入力配列 $A[p:r]$ に対して i 番目に小さい要素を求める場合を考える。&lt;br /&gt;
このアルゴリズムの実行時間は以下の漸化式で表される：
$$
T(n) \leq T(n/5) + T(7n/10) + \Theta(n)
$$&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;漸化式の展開&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;第 16、23、および 24 行における再帰呼び出しの外側でかかる時間の上界を求める。&lt;/li&gt;
&lt;li&gt;第 1-10 行の &lt;strong&gt;while ループ&lt;/strong&gt; は最大 $O(n)$ 時間で終了する。第 12-13 行で行われる 5 要素グループのソートは、グループ数 $n/5$ に基づき $O(n)$ 時間かかる。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;部分問題の削減&lt;/strong&gt;
ピボット選択後、再帰的な SELECT 呼び出しでは、次のように入力サイズが縮小される：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;ピボットの両端から除外される要素数の合計は $n/5 + (3n/10)$ 以上である。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;これをもとに、漸化式 $T(n) \leq T(n/5) + T(7n/10) + \Theta(n)$ を用いて解析を進める。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;漸化式の解&lt;/strong&gt;
漸化式を次のように展開する：&lt;/p&gt;
&lt;p&gt;$$
\begin{aligned}
T(n) &amp;amp;\leq c(n/5) + c(7n/10) + \Theta(n) \
&amp;amp;\leq cn/5 + 7cn/10 + \Theta(n) \
&amp;amp;\leq cn + \Theta(n)
\end{aligned}
$$&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h4&gt;結論&lt;/h4&gt;
&lt;p&gt;以上の解析より、SELECT の実行時間は $\Theta(n)$ である。&lt;br /&gt;
この結果は SELECT が線形時間で動作することを示している。&lt;/p&gt;
</content:encoded></item><item><title>グラフ理論(木)</title><link>https://www.shiinayane.com/ja/posts/graph-theory-trees/</link><guid isPermaLink="true">https://www.shiinayane.com/ja/posts/graph-theory-trees/</guid><pubDate>Fri, 21 Feb 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;以下はグラフ理論における木部分である。&lt;/p&gt;
&lt;h2&gt;用語 (Terms)&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;木 (Tree)&lt;/strong&gt;: &lt;strong&gt;単純閉路&lt;/strong&gt;を持たない&lt;strong&gt;連結な無向グラフ&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;森 (Forest)&lt;/strong&gt;: &lt;strong&gt;単純閉路&lt;/strong&gt;を持たない無向グラフ&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;根付き木 (Rooted Tree)&lt;/strong&gt;: &lt;strong&gt;根&lt;/strong&gt;と呼ばれる指定された頂点があり、この根から他の頂点への経路が唯一存在する有向グラフ&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;部分木 (Subtree)&lt;/strong&gt;: 木の部分グラフで、かつ木であるもの&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;親 (Parent of $v$ in a Rooted Tree)&lt;/strong&gt;: 根付き木において $(u, v)$ が辺であるときの頂点 $u$&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;子 (Child of a Vertex $v$ in a Rooted Tree)&lt;/strong&gt;: 頂点 $v$ を親とする頂点&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;兄弟 (Sibling of a Vertex $v$ in a Rooted Tree)&lt;/strong&gt;: 同じ親を持つ頂点&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;祖先 (Ancestor of a Vertex $v$)&lt;/strong&gt;: 根から $v$ までの経路上にある頂点&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;子孫 (Descendant of a Vertex $v$)&lt;/strong&gt;: $v$ を祖先とする頂点&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;内部頂点 (Internal Vertex)&lt;/strong&gt;: 子を持つ頂点&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;葉 (Leaf)&lt;/strong&gt;: 子を持たない頂点&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;頂点のレベル (Level of a Vertex)&lt;/strong&gt;: 根からその頂点への経路の長さ&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;木の高さ (Height of a Tree)&lt;/strong&gt;: 木の頂点のレベルの最大値&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;$m$-分木 ($m$-ary Tree)&lt;/strong&gt;: 各内部頂点が最大 $m$ 個の子を持つ木&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;完全$m$-分木 (Full $m$-ary Tree)&lt;/strong&gt;: 各内部頂点が正確に $m$ 個の子を持つ木&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;二分木 (Binary Tree)&lt;/strong&gt;: $m = 2$ の場合の $m$-分木&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;順序木 (Ordered Tree)&lt;/strong&gt;: 各内部頂点の子が線形順序で並べられた木&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;平衡木 (Balanced Tree)&lt;/strong&gt;: すべての葉が高さ $h$ または $h-1$ のレベルにある木&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;二分探索木 (Binary Search Tree)&lt;/strong&gt;: 頂点にラベルが付けられ、各頂点の左部分木のラベルがその頂点より小さく、右部分木のラベルがその頂点より大きい二分木&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;決定木 (Decision Tree)&lt;/strong&gt;: 各頂点が決定の結果を表し、葉が問題の解決策を表す根付き木&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;ゲーム木 (Game Tree)&lt;/strong&gt;: 頂点がゲームの状態を表し、辺が合法的な手を表す根付き木&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;接頭辞コード (Prefix Code)&lt;/strong&gt;: ある文字のコードが別の文字のコードの接頭辞とならない性質を持つコード&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;ミニマックス戦略 (Minmax Strategy)&lt;/strong&gt;: 最初のプレイヤーが最大値、次のプレイヤーが最小値を選ぶ戦略&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;頂点の値 (Value of a Vertex in a Game Tree)&lt;/strong&gt;: 葉の場合、その状態で得られる利益; 内部頂点の場合、偶数レベルでは最大値、奇数レベルでは最小値&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;木の走査 (Tree Traversal)&lt;/strong&gt;: 木の頂点をリストアップする方法&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;前順序走査 (Preorder Traversal)&lt;/strong&gt;: 根を最初に、左から順に部分木を走査する方法&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;中順序走査 (Inorder Traversal)&lt;/strong&gt;: 左部分木、根、右部分木の順で走査する方法&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;後順序走査 (Postorder Traversal)&lt;/strong&gt;: 左から順に部分木を走査し、最後に根を訪れる方法&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;中置記法 (Infix Notation)&lt;/strong&gt;: 木の中順序走査で得られる数式の表記法&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;前置記法 (Prefix Notation / Polish Notation)&lt;/strong&gt;: 木の前順序走査で得られる数式の表記法&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;後置記法 (Postfix Notation / Reverse Polish Notation)&lt;/strong&gt;: 木の後順序走査で得られる数式の表記法&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;全域木 (Spanning Tree)&lt;/strong&gt;: グラフのすべての頂点を含む木&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;最小全域木 (Minimum Spanning Tree)&lt;/strong&gt;: 辺の重みの合計が最小の全域木&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;結果 (Results)&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;木の定義 (Tree Definition)&lt;/strong&gt;: すべての頂点対に対して唯一の単純経路が存在する場合、そのグラフは木である。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;木の辺の数 (Edges in a Tree)&lt;/strong&gt;: $n$ 頂点を持つ木は $n-1$ 本の辺を持つ。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;完全$m$-分木の頂点数 (Vertices in a Full $m$-ary Tree)&lt;/strong&gt;: 内部頂点数を $i$ とすると、$mi+1$ 頂点を持つ。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;$m$-分木の葉と高さ (Leaves and Height in an $m$-ary Tree)&lt;/strong&gt;: $m$-分木で高さ $h$ の葉の数は最大 $m^h$ である。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;ハフマン符号 (Huffman Coding)&lt;/strong&gt;: 与えられた文字の頻度に基づき、最適な二分符号を構築する手順&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;深さ優先探索 (Depth-First Search, DFS)&lt;/strong&gt;: 生成木を構築する際、パスを追加し続け、進めなくなったら戻る手法&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;幅優先探索 (Breadth-First Search, BFS)&lt;/strong&gt;: 直前に追加された辺に隣接するすべての辺を順に追加して生成木を構築する手法&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;プリムのアルゴリズム (Prim’s Algorithm)&lt;/strong&gt;: 重み付きグラフで最小生成木を構築するために、既存の頂点に隣接する最小重みの辺を追加する手順&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;クラスカルのアルゴリズム (Kruskal’s Algorithm)&lt;/strong&gt;: 重み付きグラフで最小生成木を構築するために、重みが最小の辺を順に追加し、閉路を作らないようにする手順&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;定理と証明&lt;/h2&gt;
&lt;h3&gt;11.1 定理1&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;無向グラフが木であるための必要十分条件は、任意の2つの頂点間に一意な単純経路が存在することである。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;証明&lt;/strong&gt;:&lt;br /&gt;
まず、$T$ が木であると仮定する。このとき、$T$ は連結なグラフであり、単純閉路を持たない。$T$ の2つの頂点 $x$ と $y$ を考える。
$T$ が連結であるため、セクション10.4の定理1より、$x$ と $y$ の間には単純経路が存在する。
さらに、この経路は一意であることを示す。もし2つ目の経路が存在すれば、1つ目の経路を $x$ から $y$ まで進み、2つ目の経路を逆順に $y$ から $x$ に戻ることで閉路が形成される。しかし、木には単純閉路が存在しないため、矛盾が生じる。したがって、任意の2つの頂点間の単純経路は一意である。&lt;/p&gt;
&lt;p&gt;次に、任意の2つの頂点間に一意な単純経路が存在すると仮定する。このとき、$T$ は連結である。なぜなら、任意の2つの頂点間に経路が存在するからである。また、$T$ は単純閉路を持たないことを示す。
仮に単純閉路が存在するとすれば、その閉路には2つの頂点 $x$ と $y$ を結ぶ2つの異なる単純経路が存在することになる。しかし、これは仮定に反する。したがって、$T$ は単純閉路を持たない。&lt;/p&gt;
&lt;h3&gt;11.1 定理2&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;頂点数が $n$ である木は $n - 1$ 本の辺を持つ。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;証明&lt;/strong&gt;:&lt;br /&gt;
この定理を数学的帰納法を用いて証明する。ここで考えるすべての木は根付き木として扱い、根を選ぶものとする。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;基礎ステップ&lt;/strong&gt;:&lt;br /&gt;
$n = 1$ の場合、1つの頂点を持つ木には辺が存在しない。したがって、$n = 1$ では定理が成り立つ。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;帰納ステップ&lt;/strong&gt;:&lt;br /&gt;
帰納法の仮定として、$k$ 頂点を持つ木は $k - 1$ 本の辺を持つとする。$T$ を $k + 1$ 頂点を持つ木とし、$v$ を $T$ の葉と仮定する（木は有限であるため葉が存在する）。
$w$ を $v$ の親とする。$T$ から $v$ を削除し、$v$ と $w$ を結ぶ辺を取り除くと、$k$ 頂点を持つ木 $T&apos;$ が得られる。
このとき、$T&apos;$ は依然として連結であり、単純閉路を持たない。&lt;/p&gt;
&lt;p&gt;帰納法の仮定より、$T&apos;$ は $k - 1$ 本の辺を持つ。したがって、$T$ は $T&apos;$ より1本多く辺を持つ。これは $v$ と $w$ を結ぶ辺が追加されているためである。&lt;/p&gt;
&lt;h3&gt;11.1 定理3&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;内部頂点が $i$ である完全 $m$-分木は $n = mi + 1$ 個の頂点を含む。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;証明&lt;/strong&gt;:&lt;br /&gt;
根を除くすべての頂点は、内部頂点の子である。各内部頂点は $m$ 個の子を持つため、根以外の頂点は $mi$ 個存在する。したがって、この木は合計で $n = mi + 1$ 個の頂点を含む。&lt;/p&gt;
&lt;p&gt;次に、$T$ を完全 $m$-分木と仮定する。$i$ を内部頂点の数、$l$ を葉の数とする。
この木では、$n$、$i$、および $l$ のいずれか1つがわかれば、他の2つの量も決定される。&lt;br /&gt;
&lt;strong&gt;定理4&lt;/strong&gt; では、これらの量の間の関係を説明している。&lt;/p&gt;
&lt;h3&gt;11.1 定理4&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;完全 $m$-分木に関する関係式:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;$n$ 個の頂点を持つ場合:&lt;br /&gt;
内部頂点の数: $i = \frac{n-1}{m}$、葉の数: $l = \frac{(m-1)n + 1}{m}$&lt;/li&gt;
&lt;li&gt;$i$ 個の内部頂点を持つ場合:&lt;br /&gt;
頂点の数: $n = mi + 1$、葉の数: $l = (m-1)i + 1$&lt;/li&gt;
&lt;li&gt;$l$ 個の葉を持つ場合:&lt;br /&gt;
頂点の数: $n = \frac{ml - 1}{m-1}$、内部頂点の数: $i = \frac{l-1}{m-1}$&lt;/li&gt;
&lt;/ol&gt;
&lt;/blockquote&gt;
&lt;h3&gt;&lt;strong&gt;証明&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;(i) $n$ 個の頂点を持つ場合:&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;$n = mi + 1$ から $i = \frac{n-1}{m}$ を得る。&lt;/li&gt;
&lt;li&gt;これを $n = l + i$ に代入すると、葉の数 $l = \frac{(m-1)n + 1}{m}$ となる。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;(ii) $i$ 個の内部頂点が与えられた場合:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;$n$ の値は、定義から $n = mi + 1$ である。&lt;/li&gt;
&lt;li&gt;$n = l + i$ という事実を利用すると、葉の数 $l$ は次のようになる:&lt;br /&gt;
$$
l = n - i = (mi + 1) - i = (m-1)i + 1&lt;br /&gt;
$$&lt;br /&gt;
したがって、内部頂点 $i$ に対して、頂点数 $n$ と葉の数 $l$ が求まる。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;(iii) $l$ 個の葉が与えられた場合:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;頂点数 $n$ は、各内部頂点が $m$ 個の子を持つ完全 $m$-分木の性質を用いると、次のようになる:&lt;br /&gt;
$$
n = l + i \quad \text{and} \quad i = \frac{l-1}{m-1}
$$&lt;br /&gt;
この式を $n = l + i$ に代入する:&lt;br /&gt;
$$
n = l + \frac{l-1}{m-1} = \frac{(m-1)l + (l-1)}{m-1} = \frac{ml - 1}{m-1}&lt;br /&gt;
$$&lt;br /&gt;
これにより、頂点数 $n$ が導かれる。&lt;/li&gt;
&lt;li&gt;次に内部頂点 $i$ は明らかに:&lt;br /&gt;
$$
i = \frac{l-1}{m-1}&lt;br /&gt;
$$&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;11.1 定理5&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;$h$ の高さを持つ $m$-分木（$m$-ary tree）において、葉の数は最大 $m^h$ である。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;証明&lt;/strong&gt;:&lt;/p&gt;
&lt;p&gt;この証明は高さに関する数学的帰納法を用いる。&lt;/p&gt;
&lt;p&gt;まず、高さ1の $m$-分木を考える。この木は根と最大 $m$ 個の子を持ち、それらはすべて葉である。したがって、葉の数は最大で $m^1 = m$ である。これが帰納法の基礎ステップである。&lt;/p&gt;
&lt;p&gt;次に、任意の高さ $h$ 未満の $m$-分木についてこの結果が成り立つと仮定する。これを帰納法の仮定とする。&lt;br /&gt;
$T$ を高さ $h$ の $m$-分木とする。$T$ の葉は、根からレベル1の各頂点へとエッジを削除して得られる $T$ の部分木の葉である。&lt;/p&gt;
&lt;p&gt;これらの部分木の高さは $h-1$ 以下である。したがって、帰納法の仮定より、各部分木の葉の数は最大 $m^{h-1}$ である。さらに、これらの部分木の数は最大 $m$ であるため、全体の葉の数は&lt;br /&gt;
$$
m \cdot m^{h-1} = m^h
$$&lt;br /&gt;
となる。これにより、根付き木 $T$ の葉の数が最大 $m^h$ であることが示された。&lt;/p&gt;
&lt;p&gt;これで帰納法による証明が完了する。&lt;/p&gt;
&lt;h3&gt;11.1 系1&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;高さ $h$ の $m$-分木（$m$-ary tree）に葉の数が $l$ 個あるとき、&lt;br /&gt;
$$
h \geq \lceil \log_m l \rceil
$$&lt;br /&gt;
が成り立つ。もし $m$-分木が完全かつ平衡であるならば、&lt;br /&gt;
$$
h = \lceil \log_m l \rceil
$$&lt;br /&gt;
（ここで、天井関数 $\lceil x \rceil$ は、$x$ 以上の最小の整数を表す）。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;証明&lt;/strong&gt;:&lt;/p&gt;
&lt;p&gt;定理5より、葉の数 $l$ に対して $l \leq m^h$ が成り立つ。底 $m$ で対数を取ると、&lt;br /&gt;
$$
\log_m l \leq h
$$&lt;br /&gt;
となる。$h$ は整数であるため、&lt;br /&gt;
$$
h \geq \lceil \log_m l \rceil
$$&lt;br /&gt;
が成り立つ。&lt;/p&gt;
&lt;p&gt;次に、木が平衡であると仮定する。すると、各葉は高さ $h$ または $h-1$ のレベルに存在する。そして、高さが $h$ であるため、レベル $h$ に少なくとも1つの葉が存在する。
これにより、葉の数は $m^{h-1}$ より多い必要がある（演習30を参照）。&lt;br /&gt;
また、$l \leq m^h$ であるため、&lt;br /&gt;
$$
m^{h-1} &amp;lt; l \leq m^h
$$&lt;br /&gt;
が成り立つ。この不等式に底 $m$ の対数を取ると、&lt;br /&gt;
$$
h - 1 &amp;lt; \log_m l \leq h
$$&lt;br /&gt;
が得られる。&lt;/p&gt;
&lt;p&gt;したがって、$h$ は&lt;br /&gt;
$$
h = \lceil \log_m l \rceil
$$&lt;br /&gt;
であることが示される。&lt;/p&gt;
&lt;h3&gt;11.1.14&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;単純グラフ $T$ が木であるための必要十分条件は、$T$ が連結であり、かつ任意の辺を削除すると非連結グラフになることである。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;証明&lt;/strong&gt;:&lt;/p&gt;
&lt;p&gt;まず、$T$ が木であると仮定する。定義より $T$ は連結であり、任意の辺を削除すると非連結グラフになることを示す。&lt;br /&gt;
${x, y}$ を $T$ の辺とする。このとき $x \neq y$ である。$T$ から ${x, y}$ を削除したグラフは、$x$ から $y$ への経路が存在しない。
なぜなら、$T$ では $x$ から $y$ への単純な経路が唯一であり、その経路に ${x, y}$ が含まれていたからである。
（定理1より、頂点 $u$ から頂点 $v$ への経路が存在するならば、単純経路も存在する。）&lt;br /&gt;
したがって、${x, y}$ を削除すると非連結グラフになる。&lt;/p&gt;
&lt;p&gt;次に、$T$ が連結であり、任意の辺を削除すると非連結になると仮定する。$T$ が木であることを示す。  $T$ が木でない場合、$T$ には単純閉路（サイクル）が存在する。
例えば、閉路を $x_1, x_2, \dots, x_r, x_1$ とする。$T$ から辺 ${x_r, x_1}$ を削除しても、グラフは連結のままである。
なぜなら、削除された辺が経路に使用されていたとしても、閉路の他の部分（$x_1, x_2, \dots, x_r$ またはその逆順）を使って連結性を保つことができるからである。&lt;br /&gt;
これは仮定「任意の辺を削除すると非連結になる」に反する。したがって、$T$ は木である。&lt;/p&gt;
&lt;h3&gt;11.1.15&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;$n$ 個の頂点を持つ単純グラフ $G$ について、次を示せ：&lt;br /&gt;
a. $G$ は $n - 1$ 本の辺を持つ連結グラフであるとき、かつそのときに限り木である。&lt;br /&gt;
b. $G$ は $n - 1$ 本の辺を持ち、単純閉路を持たないとき、かつそのときに限り木である。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;証明&lt;/strong&gt;:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;a.&lt;/strong&gt;&lt;br /&gt;
&lt;strong&gt;「ならば」部分&lt;/strong&gt;&lt;br /&gt;
$G$ が木であるならば、それは連結であり閉路を持たない。そのため、$G$ の辺の数は $n - 1$ である（Theorem 2）。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;「必要条件」部分&lt;/strong&gt;&lt;br /&gt;
$G$ が連結な単純グラフであり、$n - 1$ 本の辺を持つと仮定する。もし $G$ が木でない場合、Exercise 14 により $G$ には取り除くことで連結なグラフ $G&apos;$ を生成できる辺が存在する。この操作を繰り返し、最終的に木が得られる。
この操作には高々 $n - 1$ 回の辺の削除が必要であるが、もともと $G$ は $n - 1$ 本の辺しか持たないため、削除は行われない。したがって、$G$ は初めから木であった。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;b.&lt;/strong&gt;&lt;br /&gt;
&lt;strong&gt;「ならば」部分&lt;/strong&gt;&lt;br /&gt;
$G$ が木であるならば、それは $n - 1$ 本の辺を持ち、定義から単純閉路を持たない。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;「必要条件」部分&lt;/strong&gt;&lt;br /&gt;
$G$ が $n - 1$ 本の辺を持ち、単純閉路を持たないと仮定する。$c$ を $G$ の連結成分の数とする。それぞれの成分が $n_i$ 個の頂点を持つとする。このとき、&lt;br /&gt;
$$
\sum_{i=1}^c n_i = n
$$
である。また、各成分について、辺の総数は $\sum_{i=1}^c (n_i - 1)$ となる。よって、&lt;br /&gt;
$$
\sum_{i=1}^c (n_i - 1) = n - c.
$$
問題の仮定より、$n - c = n - 1$ となる。したがって、$c = 1$ が導かれる。つまり、$G$ は連結であり、木の定義を満たす。&lt;/p&gt;
&lt;h3&gt;11.1.30&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;高さ $h$ の完全 $m$-分平衡木（full $m$-ary balanced tree）において、葉の数は $m^{h-1}$ よりも多いことを示せ。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;証明&lt;/strong&gt;:&lt;/p&gt;
&lt;p&gt;$m \geq 2$ と仮定する。まず、レベル $h$ にあるすべての頂点を削除する。レベル $h$ には少なくとも1つの頂点が存在し、それらはすべて葉である。&lt;br /&gt;
このとき、結果として得られる木は高さ $h-1$ の完全 $m$-分木である。演習28の結果より、この木の葉の数は $m^{h-1}$ である。&lt;/p&gt;
&lt;p&gt;しかし、元の木では、レベル $h-1$ にあるすべての内部頂点がレベル $h$ に少なくとも2つの葉を生成する。したがって、元の木の葉の数は $m^{h-1}$ よりも多くなる。&lt;/p&gt;
&lt;h3&gt;11.1.44&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;すべての木は2色で彩色することができる。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;証明&lt;/strong&gt;:&lt;/p&gt;
&lt;p&gt;根を1つ選び、それを赤色で塗る。その後、奇数レベルのすべての頂点を青色で塗り、偶数レベルのすべての頂点を赤色で塗る。この彩色方法により、隣接する頂点は必ず異なる色で塗られるため、木全体が2色で彩色可能である。&lt;/p&gt;
&lt;h3&gt;11.1.48&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;$n$ 頂点を持つ二分木において、葉の平均深さが $\Omega(\log n)$ であることを示せ。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;証明&lt;/strong&gt;:&lt;/p&gt;
&lt;p&gt;$T$ を高さ $h$ を持つ $n$ 頂点の二分木とする。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;頂点の移動&lt;/strong&gt;:&lt;br /&gt;
$h-1$ のレベルに子が2つない内部頂点が存在する場合、レベル $h$ にある葉をその欠損部分の子として移動させる。この操作によって、木の葉の平均深さは低くなるが、証明すべき下限には影響しない。
したがって、操作後の木について証明すれば十分である。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;完全な二分木の構築&lt;/strong&gt;:&lt;br /&gt;
この操作を繰り返すことで、$h-1$ レベル以下に2つの子を持たない内部頂点が存在しなくなる。結果として、すべての葉はレベル $h-1$ および $h$ に集まる。
次に、レベル $h$ にあるすべての頂点を削除し、レベル $h-1$ に集約する。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;頂点数の計算&lt;/strong&gt;:&lt;br /&gt;
頂点数 $n$ に対する変化は最大でも係数2（またはそれより少し多い程度）であり、大きなオメガ記法（$\Omega$）に対する影響は無視できる（$\log n$ が1程度しか変化しないため）。&lt;br /&gt;
完全二分木では、演習28より、葉の数は $2^{h-1}$ であり、$n = 2^h - 1$ が成り立つ。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;葉の平均深さ&lt;/strong&gt;:&lt;br /&gt;
完全二分木において、すべての葉は深さ $h-1$ にある。ここで、$n = 2^h - 1$ より、&lt;br /&gt;
$$
h \approx \log_2 n
$$&lt;br /&gt;
したがって、葉の平均深さは $\Omega(\log n)$ であることが示された。&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;11.2 定理1&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;二分比較に基づくソートアルゴリズムは、少なくとも $\lceil \log_2 n! \rceil$ 回の比較を必要とする。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;証明&lt;/strong&gt;:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;ソートの複雑度の定義&lt;/strong&gt;:&lt;br /&gt;
ソートアルゴリズムの複雑度は、使用される二分比較の回数で測定される。最悪の場合の比較回数は、ソート手順を表す決定木（decision tree）の高さに等しい。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;決定木と高さの関係&lt;/strong&gt;:&lt;br /&gt;
$n$ 要素のリストをソートするための決定木には、$n!$ 通りの葉が存在する。これは、ソートのすべての順列に対応する。&lt;br /&gt;
二分木の高さは、葉の数 $n!$ に対して&lt;br /&gt;
$$
\lceil \log_2 n! \rceil
$$&lt;br /&gt;
以上であることが系1（11.1節）から導かれる。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;結論&lt;/strong&gt;:&lt;br /&gt;
よって、$n!$ 個の葉を持つ二分決定木の高さは少なくとも $\lceil \log_2 n! \rceil$ であり、これはソートアルゴリズムに必要な比較回数の下限である。&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;11.2 系1&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;二分比較に基づくソートアルゴリズムが $n$ 個の要素をソートする際の比較回数は $\Omega(n \log n)$ である。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;証明&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;定理1の結果&lt;/strong&gt;: 定理1より、二分比較に基づくソートアルゴリズムが必要とする比較回数は少なくとも $\lceil \log_2 n! \rceil$ である。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;階乗の対数の評価&lt;/strong&gt;:&lt;br /&gt;
演習74（Section 3.2）より、$\log_2 n!$ は $\Theta(n \log n)$ である。これは、アルゴリズムの計算量解析における標準的な参照関数である。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;結論&lt;/strong&gt;:&lt;br /&gt;
よって、$n$ 個の要素をソートするために二分比較を使用するアルゴリズムは、最悪の場合において少なくとも&lt;br /&gt;
$$
\Omega(n \log n)
$$&lt;br /&gt;
回の比較が必要である。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;これにより、系1が示された。&lt;/p&gt;
&lt;h3&gt;11.2 定理2&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;二分比較に基づくソートアルゴリズムが $n$ 個の要素をソートする際に使用する平均比較回数は $\Omega(n \log n)$ である。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;証明&lt;/strong&gt;:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;最悪ケースの比較回数&lt;/strong&gt;:&lt;br /&gt;
系1より、二分比較に基づくソートアルゴリズムが $n$ 個の要素をソートする際の最悪ケースの比較回数は $\Theta(n \log n)$ である。これにより、マージソートのようなアルゴリズムがこの複雑度において最適であることが分かる。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;平均ケースの証明&lt;/strong&gt;:&lt;br /&gt;
平均ケースでも類似の結果が成り立つことを示す。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;平均比較回数は、決定木における葉の平均深さに等しい。&lt;/li&gt;
&lt;li&gt;演習48（11.1節）より、$N$ 頂点を持つ二分木における葉の平均深さは $\Omega(\log N)$ である。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;階乗関係の利用&lt;/strong&gt;:&lt;br /&gt;
$N = n!$ とおくと、葉の平均深さが $\Omega(\log N)$ であることから、$N = n!$ に対応する関数 $\log n!$ は $\Theta(n \log n)$ である。
したがって、平均比較回数も&lt;br /&gt;
$$
\Omega(n \log n)
$$&lt;br /&gt;
である。&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;以上により、定理3が示された。&lt;/p&gt;
&lt;h3&gt;11.4 定理1&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;単純グラフ $G$ は、全域木を持つ場合に限り連結である。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;証明&lt;/strong&gt;:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;（必要条件）&lt;/strong&gt;&lt;br /&gt;
まず、単純グラフ $G$ が全域木 $T$ を持つと仮定する。&lt;br /&gt;
$T$ は $G$ の全ての頂点を含み、さらにその頂点間にパスが存在する。このとき、$T$ は $G$ の部分グラフであるため、$G$ においても任意の2つの頂点の間にパスが存在する。したがって、$G$ は連結である。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;（十分条件）&lt;/strong&gt;&lt;br /&gt;
次に、$G$ が連結であると仮定する。&lt;br /&gt;
もし $G$ が木でない場合、$G$ は単純閉路（サイクル）を含む。その閉路から1つの辺を削除する。このとき、生成される部分グラフは1本少ない辺を持ちながら、$G$ の全ての頂点を含み、かつ連結である。この部分グラフは依然として連結である。なぜなら、削除された辺を含む閉路上の任意の2つの頂点は、削除された辺を含まないパスによって依然として結ばれるためである。&lt;/p&gt;
&lt;p&gt;この操作を繰り返すことで、$G$ 内の全ての単純閉路が削除される。グラフに含まれる辺の数は有限であるため、このプロセスは有限回の手順で終了する。その結果として、閉路を持たない連結なグラフが残る。このグラフは木であり、$G$ の全ての頂点を含むため、全域木である。&lt;/p&gt;
&lt;h3&gt;11.4.25&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;連結単純グラフ $G$ において、頂点 $v$ を根とする幅優先全域木（breadth-first spanning tree）における $u$ のレベル数は、$v$ から $u$ への最短経路の長さに等しいことを示せ。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;証明&lt;/strong&gt;:&lt;/p&gt;
&lt;p&gt;帰納法を経路の長さに基づいて示す。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;基礎ステップ（経路の長さが0の場合）&lt;/strong&gt;:&lt;br /&gt;
経路の長さが0であれば、$v = u$ であり、結果は自明である。$u$ は根 $v$ と同じレベル（レベル0）にある。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;経路の長さが1の場合&lt;/strong&gt;:&lt;br /&gt;
経路の長さが1の場合、$u$ は $v$ に隣接している。したがって、幅優先全域木において $u$ はレベル1に配置される。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;帰納法の仮定&lt;/strong&gt;:&lt;br /&gt;
経路の長さが $l$ 以下のすべての頂点 $u&apos;$ に対して、$u&apos;$ のレベルは $v$ から $u&apos;$ への最短経路の長さに等しいと仮定する。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;経路の長さが $l+1$ の場合&lt;/strong&gt;:&lt;br /&gt;
経路の長さが $l+1$ の場合、$v$ から $u$ への最短経路上にある $u$ の直前の頂点を $u&apos;$ とする。
帰納法の仮定より、$u&apos;$ は幅優先全域木のレベル $l$ にある。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;$u$ がレベル $l$ 以下にあったと仮定すると、$v$ から $u$ への最短経路の長さも $l$ 以下になる。これは $u$ が直前の頂点 $u&apos;$ に隣接していることに矛盾する。&lt;/li&gt;
&lt;li&gt;よって、$u$ は幅優先全域木において $u&apos;$ の隣接頂点として、レベル $l+1$ に追加される。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;以上により、帰納法により証明が成立する。&lt;/p&gt;
&lt;h3&gt;11.4.55&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;$T_1$ および $T_2$ を単純グラフ $G$ の2つの全域木（spanning trees）とする。また、$e_1$ を $T_1$ に含まれ、$T_2$ に含まれない辺とする。
このとき、$T_2$ に含まれ、$T_1$ に含まれない辺 $e_2$ が存在して、次の性質が成り立つことを示せ：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;$T_1$ から $e_1$ を削除し、$e_2$ を加えたものは全域木である。&lt;/li&gt;
&lt;li&gt;$T_2$ から $e_2$ を削除し、$e_1$ を加えたものは全域木である。&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;証明&lt;/strong&gt;:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;$T_2 \cup {e_1}$ における閉路の存在&lt;/strong&gt;:&lt;br /&gt;
$e_1$ を $T_2$ に加えると、$T_2 \cup {e_1}$ は単純閉路 $C$ を含む。この閉路 $C$ は $e_1$ を含む。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;$T_1 - {e_1}$ の連結成分&lt;/strong&gt;:&lt;br /&gt;
$T_1$ から $e_1$ を削除すると、グラフ $T_1 - {e_1}$ は2つの連結成分に分かれる。$e_1$ の端点 $u$ と $v$ は、これら2つの異なる成分に属する。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;閉路 $C$ 上の $e_2$ の選択&lt;/strong&gt;:&lt;br /&gt;
$C$ 上を $u$ から $e_1$ の方向とは逆に進むと、$v$ と同じ成分に初めて到達する頂点が存在する。その際に交差する辺を $e_2$ とする。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;$T_2 \cup {e_1} - {e_2}$ は全域木である&lt;/strong&gt;:&lt;br /&gt;
$e_2$ は閉路 $C$ 上に存在するため、$e_2$ を削除すると閉路が解消される。したがって、$T_2 \cup {e_1} - {e_2}$ は依然として連結かつ $G$ のすべての頂点を含む木となる。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;$T_1 - {e_1} \cup {e_2}$ は全域木である&lt;/strong&gt;:&lt;br /&gt;
$T_1 - {e_1}$ は2つの連結成分に分かれているが、$e_2$ はこの2つの成分を結合する。したがって、$T_1 - {e_1} \cup {e_2}$ は再び連結かつすべての頂点を含む木となる。&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;結論&lt;/strong&gt;:&lt;br /&gt;
以上により、$e_2$ は条件を満たす辺であり、次の性質が示された：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;$T_1 - {e_1} \cup {e_2}$ は全域木である。&lt;/li&gt;
&lt;li&gt;$T_2 \cup {e_1} - {e_2}$ は全域木である。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;11.4.56&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;任意の全域木 $T_1$ から任意の別の全域木 $T_2$ へ、辺を1つずつ削除し別の辺を加える操作を繰り返すことで到達できることを示せ。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;証明&lt;/strong&gt;:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;前問（Exercise 55）の利用&lt;/strong&gt;:&lt;br /&gt;
問題55により、$T_1$ に含まれ $T_2$ には含まれない辺 $e_1$ を削除し、
$T_2$ に含まれ $T_1$ には含まれない辺 $e_2$ を加えることで、$T_1$ を新しい全域木に変換することが可能である。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;距離の定義&lt;/strong&gt;:&lt;br /&gt;
$T_1$ と $T_2$ の間の&lt;strong&gt;距離&lt;/strong&gt; $d$ は、それらの木の辺集合の対称差（$T_1$ と $T_2$ の共通でない辺の数）を2で割ったものである（各操作で2つの辺が入れ替わるため）。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;操作による変換&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;一度の操作で、$T_1$ に含まれる辺 $e_1$ を削除し、$T_2$ に含まれる辺 $e_2$ を加えることで、$T_1$ と $T_2$ の間の距離を2減少させることができる。&lt;/li&gt;
&lt;li&gt;この操作を繰り返すことで、距離 $d$ を0にすることができる。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;帰納法的な手続き&lt;/strong&gt;:&lt;br /&gt;
初めに $T_1$ と $T_2$ の距離が $d$ であるとする。各ステップで次の操作を行う：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;$T_1$ に含まれるが $T_2$ には含まれない辺 $e_1$ を選ぶ。&lt;/li&gt;
&lt;li&gt;$T_2$ に含まれるが $T_1$ には含まれない辺 $e_2$ を選び、$T_1$ から $e_1$ を削除し、$e_2$ を加える。&lt;br /&gt;
この操作により、距離 $d$ は2減少する。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;終了条件&lt;/strong&gt;:&lt;br /&gt;
距離 $d$ が0になるとき、$T_1$ は $T_2$ と完全に一致する。&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;結論&lt;/strong&gt;:&lt;br /&gt;
この手順を $d$ 回繰り返すことで、任意の全域木 $T_1$ から任意の別の全域木 $T_2$ へ到達することができる。&lt;/p&gt;
&lt;h3&gt;11.5.18&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;連結な重み付きグラフにおいて、最小重みの辺は任意の最小全域木に含まれなければならないことを示せ。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;証明&lt;/strong&gt;:&lt;/p&gt;
&lt;p&gt;反証法を用いる。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;仮定&lt;/strong&gt;:&lt;br /&gt;
最小重みの辺 $e$ がある最小全域木 $T$ に含まれていないと仮定する。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;辺 $e$ の追加による閉路の形成&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;$e$ を $T$ に追加すると、$T \cup {e}$ はグラフ全体を覆うが、閉路（単純サイクル）$C$ が1つ含まれることになる。&lt;/li&gt;
&lt;li&gt;閉路 $C$ は $e$ を含む。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;閉路から辺を削除&lt;/strong&gt;:&lt;br /&gt;
閉路 $C$ には $e$ 以外の辺も存在し、それらは $e$ よりも大きな重みを持つ（仮定より $T$ の辺はすべて $e$ よりも重みが大きい）。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;$C$ の中から $e$ 以外の任意の辺 $e&apos;$ を削除すると、新しいグラフ $T&apos; = T \cup {e} - {e&apos;}$ は連結かつサイクルを含まないため、再び全域木となる。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;矛盾の導出&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;$e&apos;$ の重みは $e$ より大きいため、$T&apos;$ の重みの合計は $T$ の重みより小さくなる。&lt;/li&gt;
&lt;li&gt;これは $T$ が最小全域木であるという仮定に矛盾する。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;結論&lt;/strong&gt;:&lt;br /&gt;
したがって、最小重みの辺 $e$ は任意の最小全域木に含まれなければならない。&lt;/p&gt;
&lt;h3&gt;11.5.19&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;すべての辺の重みが異なる場合、連結な重み付きグラフにおける最小全域木は一意であることを示せ。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;証明&lt;/strong&gt;:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;仮定&lt;/strong&gt;:&lt;br /&gt;
連結な重み付きグラフ $G$ において、すべての辺の重みが異なるとする。さらに、最小全域木が2つ存在すると仮定し、それらを $T_1$ と $T_2$ とする。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;反証法の適用&lt;/strong&gt;:&lt;br /&gt;
$T_1$ と $T_2$ は異なる全域木であるため、辺の集合において異なる部分が存在する。つまり、$T_1$ に含まれ $T_2$ に含まれない辺 $e_1$ が少なくとも1つ存在する。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;$e_1$ を $T_2$ に加えると閉路 $C$ が形成される。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;閉路 $C$ に含まれる辺&lt;/strong&gt;:&lt;br /&gt;
$C$ の中には $e_1$ 以外に $T_2$ に含まれる辺が存在する。その中から任意の辺 $e_2$ を選ぶと、$e_1$ の重みと $e_2$ の重みを比較することができる。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;重みの矛盾&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;$e_1$ の重みが $e_2$ より小さい場合、$T_2$ から $e_2$ を削除し $e_1$ を加えることで、$T_2$ の重みの合計は小さくなる。
これは $T_2$ が最小全域木であるという仮定に矛盾する。&lt;/li&gt;
&lt;li&gt;$e_1$ の重みが $e_2$ より大きい場合、$T_1$ に対して同様の操作を行うと $T_1$ の重みの合計が小さくなる。これは $T_1$ が最小全域木であるという仮定に矛盾する。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;結論&lt;/strong&gt;:&lt;br /&gt;
すべての辺の重みが異なる場合、最小全域木は一意に決定される。したがって、仮定した2つの異なる最小全域木 $T_1$ と $T_2$ は存在しない。&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;補完1&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;単純グラフが木であるための必要十分条件は、次の2つを満たすことである：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;単純閉路を含まない。&lt;/li&gt;
&lt;li&gt;隣接していない2つの頂点を結ぶ辺を追加すると、ちょうど1つの単純閉路を持つ新しいグラフが生成される。&lt;/li&gt;
&lt;/ol&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;証明:&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;「ならば」部分&lt;/strong&gt;&lt;br /&gt;
$T$ が木であると仮定する。このとき、$T$ は明らかに単純閉路を含まない。&lt;br /&gt;
$T$ に隣接していない頂点 $u$ と $v$ を結ぶ辺 $e$ を追加すると、$e$ により新しいグラフに閉路が形成される。これは、$e$ と $u$ から $v$ への $T$ 内の唯一のパスによって構成される。
$T$ が木であるため、辺 $e$ の追加により生成される閉路は1つのみである。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;「十分条件」部分&lt;/strong&gt;&lt;br /&gt;
$T$ が定理の条件を満たすと仮定する。このとき、$T$ が連結であることを示せばよい。&lt;br /&gt;
$T$ が連結でないと仮定する。この場合、$u$ と $v$ が異なる連結成分に存在する場合、辺 $e = {u, v}$ を追加しても単純閉路は形成されない。これは、定理の条件に矛盾する。したがって、$T$ は連結である。&lt;/p&gt;
&lt;p&gt;さらに、$T$ は単純閉路を持たないことが仮定されているため、$T$ は木の定義を満たす。&lt;/p&gt;
&lt;h3&gt;補完3&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;少なくとも1本の辺を持つすべての木は、少なくとも2つのペンダント頂点（次数が1の頂点）を持つ。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;証明:&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;木 $T$ が $n$ 個の頂点を持ち、それぞれの頂点の次数を $d_1, d_2, \dots, d_n$ とする。木の性質から、次の式が成り立つ：&lt;br /&gt;
$$
2e = \sum_{i=1}^n d_i
$$
ここで、$e$ は木の辺の本数である。また、木では $e = n - 1$ であるため：&lt;br /&gt;
$$
2(n - 1) = \sum_{i=1}^n d_i
$$&lt;/p&gt;
&lt;p&gt;各頂点の次数 $d_i$ は少なくとも1以上であるため：&lt;br /&gt;
$$
2(n - 1) = n + \sum_{i=1}^n (d_i - 1)
$$
これを整理すると：&lt;br /&gt;
$$
n - 2 = \sum_{i=1}^n (d_i - 1)
$$&lt;/p&gt;
&lt;p&gt;この式から、和の中で $d_i - 1$ が1以上となる項は高々 $n - 2$ 個しか存在し得ない。したがって、少なくとも2つの頂点に対して $d_i - 1 = 0$ が成り立つ、すなわち $d_i = 1$ である。&lt;/p&gt;
&lt;p&gt;これにより、少なくとも2つの頂点がペンダント頂点であることが示された。&lt;/p&gt;
&lt;h3&gt;補完6&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;$d_1, d_2, \ldots, d_n$ を正の整数とし、その和が $2n - 2$ であるとする。このとき、$n$ 個の頂点を持つ木で、頂点の次数が $d_1, d_2, \ldots, d_n$ であるものが存在することを示せ。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;証明&lt;/strong&gt;:
数学的帰納法を用いて示す。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;基本ステップ&lt;/strong&gt;&lt;br /&gt;
$n \leq 2$ の場合、問題は自明である。実際、$n = 2$ の場合、$d_1 = d_2 = 1$ であり、1本の辺を持つ木がこの条件を満たす。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;帰納ステップ&lt;/strong&gt;&lt;br /&gt;
$n \geq 3$ と仮定する。このとき、正の整数 $d_i$ の少なくとも1つは $1$ である必要がある。
なぜなら、$n$ 個のすべての $d_i$ が2以上である場合、その和は少なくとも $2n$ となり、条件 $\sum d_i = 2n - 2$ に矛盾するためである。&lt;/p&gt;
&lt;p&gt;一般性を失うことなく、$d_n = 1$ と仮定する。このとき、残りの $d_1, d_2, \ldots, d_{n-1}$ がすべて1であることはあり得ない。なぜなら、条件 $\sum d_i = 2n - 2$ により、$2n - 2 &amp;gt; n$ であるためである。&lt;/p&gt;
&lt;p&gt;次に、帰納法の仮定を用いて、列 $d_1 - 1, d_2, \ldots, d_{n-1}$ に対して木が存在することを仮定する。
この木に新しい頂点を追加し、次数が $d_n = 1$ であるように辺を接続することで、頂点の次数が $d_1, d_2, \ldots, d_n$ である木を構築できる。&lt;/p&gt;
&lt;p&gt;したがって、すべての $n$ に対して定理が成立することが示された。&lt;/p&gt;
&lt;h3&gt;補完7&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;すべての木は平面グラフであることを示せ。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;証明&lt;/strong&gt;：&lt;/p&gt;
&lt;p&gt;木は単純閉路を持たないため、$K_{3,3}$ や $K_5$ の部分グラフに同相であるような部分グラフを含むことはない。したがって、木は平面グラフである。&lt;/p&gt;
&lt;h3&gt;補完8&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;すべての木は二部グラフであることを示せ。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;証明&lt;/strong&gt;：&lt;/p&gt;
&lt;p&gt;木を根付き木として考える。頂点を偶数レベルの頂点集合と奇数レベルの頂点集合に分ける。この分割により、隣接する任意の2頂点は異なる集合に属するため、木は二部グラフである。&lt;/p&gt;
&lt;h3&gt;補完9&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;すべての森は2色で彩色可能であることを示せ。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;証明&lt;/strong&gt;：&lt;/p&gt;
&lt;p&gt;森の各連結成分に対して、それぞれ独立に彩色を行う。まず、各連結成分を根付き木として設定する。その後、偶数レベルのすべての頂点を赤色で、奇数レベルのすべての頂点を青色で彩色する。これにより、各連結成分が正しく彩色され、森全体が2色で彩色可能であることが示される。&lt;/p&gt;
</content:encoded></item><item><title>グラフ理論(グラフ)</title><link>https://www.shiinayane.com/ja/posts/graph-theory-graphs/</link><guid isPermaLink="true">https://www.shiinayane.com/ja/posts/graph-theory-graphs/</guid><pubDate>Fri, 21 Feb 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;以下はグラフ理論におけるグラフ部分である。&lt;/p&gt;
&lt;h2&gt;用語&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;無向辺&lt;/strong&gt;（&lt;em&gt;undirected edge&lt;/em&gt;）：頂点 $u$ と $v$ を結ぶ辺で、順序は考慮しない。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;有向辺&lt;/strong&gt;（&lt;em&gt;directed edge&lt;/em&gt;）：頂点 $u$ から頂点 $v$ への順序付きペア $(u, v)$ に関連付けられた辺。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;重複辺&lt;/strong&gt;（&lt;em&gt;multiple edges&lt;/em&gt;）：同じ2つの頂点を結ぶ異なる辺。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;重複有向辺&lt;/strong&gt;（&lt;em&gt;multiple directed edges&lt;/em&gt;）：同じ順序付きペア $(u, v)$ に関連付けられた異なる有向辺。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;ループ&lt;/strong&gt;（&lt;em&gt;loop&lt;/em&gt;）：頂点とその自身を結ぶ辺。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;無向グラフ&lt;/strong&gt;（&lt;em&gt;undirected graph&lt;/em&gt;）：頂点の集合と無向辺の集合からなるグラフ。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;単純グラフ&lt;/strong&gt;（&lt;em&gt;simple graph&lt;/em&gt;）：重複辺やループを持たない無向グラフ。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;多重グラフ&lt;/strong&gt;（&lt;em&gt;multigraph&lt;/em&gt;）：重複辺を含むが、ループを含まない無向グラフ。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;擬似グラフ&lt;/strong&gt;（&lt;em&gt;pseudograph&lt;/em&gt;）：重複辺およびループを含む無向グラフ。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;有向グラフ&lt;/strong&gt;（&lt;em&gt;directed graph&lt;/em&gt;）：有向辺の集合を持つ頂点集合。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;有向多重グラフ&lt;/strong&gt;（&lt;em&gt;directed multigraph&lt;/em&gt;）：重複する有向辺を含むグラフ。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;単純有向グラフ&lt;/strong&gt;（&lt;em&gt;simple directed graph&lt;/em&gt;）：ループや重複有向辺のない有向グラフ。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;隣接&lt;/strong&gt;（&lt;em&gt;adjacent&lt;/em&gt;）：2つの頂点の間に辺がある場合、それらは隣接している。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;接続&lt;/strong&gt;（&lt;em&gt;incident&lt;/em&gt;）：辺がある頂点の端点である場合、その辺はその頂点に接続している。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;次数&lt;/strong&gt;（&lt;em&gt;degree $\text{deg}(v)$&lt;/em&gt;）：無向グラフにおける頂点 $v$ に接続する辺の数（ループは2回カウントする）。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;入次数&lt;/strong&gt;（&lt;em&gt;in-degree $\text{deg}^-(v)$&lt;/em&gt;）：有向グラフにおいて、頂点 $v$ を終端とする辺の数。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;出次数&lt;/strong&gt;（&lt;em&gt;out-degree $\text{deg}^+(v)$&lt;/em&gt;）：有向グラフにおいて、頂点 $v$ を始端とする辺の数。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;基底無向グラフ&lt;/strong&gt;（&lt;em&gt;underlying undirected graph&lt;/em&gt;）：有向グラフから辺の方向を無視して得られる無向グラフ。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;完全グラフ&lt;/strong&gt;（&lt;em&gt;complete graph $K_n$&lt;/em&gt;）：$n$ 頂点の全てのペアが辺で接続されたグラフ。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;二部グラフ&lt;/strong&gt;（&lt;em&gt;bipartite graph&lt;/em&gt;）：頂点集合が $V_1$ と $V_2$ に分割され、各辺が $V_1$ の頂点と $V_2$ の頂点を結ぶグラフ。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;完全二部グラフ&lt;/strong&gt;（&lt;em&gt;complete bipartite graph $K_{m,n}$&lt;/em&gt;）：頂点集合が $m$ 要素と $n$ 要素に分割され、異なる部分集合の頂点間にのみ辺が存在するグラフ。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;サイクル&lt;/strong&gt;（&lt;em&gt;cycle $C_n$&lt;/em&gt;）：$n \geq 3$ の頂点を順序よく辺で結んだ閉じた経路。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;ホイール&lt;/strong&gt;（&lt;em&gt;wheel $W_n$&lt;/em&gt;）：$C_n$ のサイクルに中心の頂点を追加し、それを全てのサイクルの頂点と結ぶグラフ。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;ハイパーキューブ&lt;/strong&gt;（&lt;em&gt;n-cube $Q_n$&lt;/em&gt;）：長さ $n$ のビット列を頂点とし、1ビットだけ異なる頂点同士を結ぶグラフ。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;結果&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;握手定理&lt;/strong&gt;（&lt;em&gt;The handshaking theorem&lt;/em&gt;）：$G = (V, E)$ が $m$ 本の辺を持つ無向グラフのとき、
$$
2m = \sum_{v \in V} \text{deg}(v)
$$
が成り立つ。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;ホールの結婚定理&lt;/strong&gt;（&lt;em&gt;Hall’s marriage theorem&lt;/em&gt;）：$G = (V, E)$ が二部グラフで、その分割が $(V_1, V_2)$ であるとき、&lt;br /&gt;
全ての部分集合 $A\subseteq V_1$ に対して $|N(A)| \geq |A|$ であれば、$V_1$ から $V_2$ への完全マッチングが存在する。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;オイラー閉路&lt;/strong&gt;（&lt;em&gt;Euler circuit&lt;/em&gt;）：連結な多重グラフがオイラー閉路を持つための必要十分条件は、全ての頂点の次数が偶数であること。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;オイラーパス&lt;/strong&gt;（&lt;em&gt;Euler path&lt;/em&gt;）：連結な多重グラフがオイラーパスを持つための必要十分条件は、次数が奇数の頂点が高々2つ存在すること。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;ダイクストラのアルゴリズム&lt;/strong&gt;（&lt;em&gt;Dijkstra’s algorithm&lt;/em&gt;）：重み付きグラフにおいて、2つの頂点間の最短経路を求める手続き。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;オイラーの公式&lt;/strong&gt;（&lt;em&gt;Euler’s formula&lt;/em&gt;）：$r = e - v + 2$。ここで $r$ は平面グラフの領域の数、$e$ は辺の数、$v$ は頂点の数。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;クラトフスキーの定理&lt;/strong&gt;（&lt;em&gt;Kuratowski’s theorem&lt;/em&gt;）：グラフが非平面であるための必要十分条件は、そのグラフが $K_{3,3}$ または $K_5$ に同型な部分グラフを含むことである。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;四色定理&lt;/strong&gt;（&lt;em&gt;The four color theorem&lt;/em&gt;）：任意の平面グラフは4色以内で彩色できる。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;定理と証明&lt;/h2&gt;
&lt;h3&gt;10.1.11&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;$G$ を単純グラフとします。$G$ の頂点の集合上の関係 $R$ を次のように定義します：$uRv$ であるのは、${u, v}$ に関連付けられた辺が存在するとき、かつそのときに限ります。
このとき、$R$ が $G$ 上の対称的で反射性を持たない関係であることを示しなさい。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;$R$ が対称的であることを示します。もし $uRv$ であれば、${u, v}$ に関連付けられた辺が存在します。
そして、集合 ${u, v} = {v, u}$ であるため、この辺は ${v, u}$ にも関連付けられます。したがって、$vRu$ が成立します。このことから、定義により $R$ は対称的な関係です。&lt;/p&gt;
&lt;p&gt;次に、$R$ が反射性を持たないことを示します。単純グラフでは自己ループが許されないため、$uRu$ は決して成立しません。このことから、定義により $R$ は反射性を持たない関係です。&lt;/p&gt;
&lt;h3&gt;10.1.12&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;$G$ をすべての頂点に自己ループを持つ無向グラフとします。$G$ の頂点の集合上の関係 $R$ を次のように定義します：$uRv$ であるのは、${u, v}$ に関連付けられた辺が存在するとき、かつそのときに限ります。
このとき、$R$ が $G$ 上の対称的で反射的な関係であることを示しなさい。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;$R$ が対称的であることを示します。もし $uRv$ であれば、頂点 $u$ と $v$ を結ぶ辺が存在します。グラフが無向であるため、この辺は同時に頂点 $v$ と $u$ を結びます。
したがって、$vRu$ が成立します。このことから、$R$ は対称的な関係です。&lt;/p&gt;
&lt;p&gt;次に、$R$ が反射的であることを示します。すべての頂点に自己ループが存在するため、任意の頂点 $u$ に対して $uRu$ が成立します。このことから、$R$ は反射的な関係です。&lt;/p&gt;
&lt;h3&gt;ハンドシェイク定理 (握手補題)&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;$G = (V, E)$ を辺が $m$ 本ある無向グラフとします。このとき、
$$
2m = \sum_{v \in V} \mathrm{deg}(v)
$$
が成り立ちます。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;証明&lt;/strong&gt;:&lt;br /&gt;
無向グラフの各辺はちょうど2つの頂点に付随しており、それぞれの頂点の次数に寄与します。
したがって、すべての頂点の次数の和はグラフ内の辺の数を2倍したものになります。このことから、式 $2m = \sum_{v \in V} \mathrm{deg}(v)$ が成立します。これは、重辺や自己ループが存在する場合にも適用されます。&lt;/p&gt;
&lt;h3&gt;10.2 定理2&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;無向グラフでは、奇数次数の頂点の数は偶数である。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;証明&lt;/strong&gt;:&lt;br /&gt;
$V_1$ を偶数次数の頂点の集合、$V_2$ を奇数次数の頂点の集合とします。無向グラフ $G = (V, E)$ において、$m$ を辺の数とすると次が成り立ちます：
$$
2m = \sum_{v \in V} \mathrm{deg}(v) = \sum_{v \in V_1} \mathrm{deg}(v) + \sum_{v \in V_2} \mathrm{deg}(v).
$$
$v \in V_1$ の場合、$\mathrm{deg}(v)$ は偶数なので、右辺の最初の項は偶数になります。また、式全体が $2m$（偶数）であるため、右辺の2つの項の和も偶数になります。
したがって、右辺の第2項 $\sum_{v \in V_2} \mathrm{deg}(v)$ も偶数でなければなりません。&lt;/p&gt;
&lt;p&gt;$V_2$ に含まれるすべての次数 $\mathrm{deg}(v)$ は奇数であるため、その和が偶数であるには、奇数項の数が偶数でなければなりません。したがって、奇数次数の頂点の数は偶数です。&lt;/p&gt;
&lt;h3&gt;10.2 定理3&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;$G = (V, E)$ を有向辺を持つグラフとします。このとき、
$$
\sum_{v \in V} \deg^-(v) = \sum_{v \in V} \deg^+(v) = |E|
$$
が成り立ちます。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;証明&lt;/strong&gt;:&lt;br /&gt;
各辺には始点（初期頂点）と終点（終端頂点）が存在するため、すべての頂点の入次数の和と出次数の和は等しくなります。これらの和はいずれもグラフに存在する辺の総数 $|E|$ に等しいことが示されます。&lt;/p&gt;
&lt;h3&gt;10.2 定理4&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;単純グラフが二部グラフであるのは、隣接する2つの頂点が同じ色に塗られないように、グラフの各頂点に2つの異なる色のいずれかを割り当てることが可能である場合、かつその場合に限ります。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;証明&lt;/strong&gt;:&lt;br /&gt;
まず、$G = (V, E)$ が二部グラフであると仮定します。
このとき、$V = V_1 \cup V_2$ であり、$V_1$ と $V_2$ は互いに頂点集合であり、$E$ の各辺は $V_1$ の頂点と $V_2$ の頂点を結びます。
$V_1$ の各頂点に1つ目の色を、$V_2$ の各頂点に2つ目の色を割り当てると、隣接する2つの頂点が同じ色になることはありません。&lt;/p&gt;
&lt;p&gt;次に、グラフの頂点に2色を割り当て、隣接する2つの頂点が同じ色に塗られないようにすることが可能であると仮定します。
このとき、1つ目の色が割り当てられた頂点の集合を $V_1$、2つ目の色が割り当てられた頂点の集合を $V_2$ とします。
このとき、$V_1$ と $V_2$ は互いに頂点集合であり、$V = V_1 \cup V_2$ となります。
また、隣接する頂点が同じ集合に属することはないため、各辺は $V_1$ の頂点と $V_2$ の頂点を結びます。したがって、$G$ は二部グラフです。&lt;/p&gt;
&lt;h3&gt;Hallの結婚定理&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;二部グラフ $G = (V, E)$ が分割 $(V_1, V_2)$ を持つとします。
このとき、$V_1$ から $V_2$ への完全マッチングが存在するのは、任意の $V_1$ の部分集合 $A$ に対して、次の条件が満たされる場合、かつその場合に限ります：
$$
|N(A)| \geq |A|.
$$&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;証明&lt;/strong&gt;:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;必要条件 ($|N(A)| \geq |A|$) を示す部分&lt;/strong&gt;:&lt;br /&gt;
まず、$V_1$ から $V_2$ への完全マッチング $M$ が存在すると仮定します。
このとき、任意の $A \subseteq V_1$ に対して、各 $v \in A$ はマッチング $M$ の辺によって $V_2$ の頂点に接続されます。
したがって、$N(A)$ の頂点数は少なくとも $A$ の頂点数に等しいか、それ以上である必要があります。つまり、$|N(A)| \geq |A|$ が成り立ちます。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;十分条件 ($|N(A)| \geq |A|$) を示す部分&lt;/strong&gt;:&lt;br /&gt;
ここでは、$|N(A)| \geq |A|$ がすべての $A \subseteq V_1$ に対して成り立つと仮定し、
このとき $V_1$ から $V_2$ への完全マッチング $M$ が存在することを示します。これを示すために、$|V_1|$ に関する数学的帰納法を用います。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;基底の場合&lt;/strong&gt;:&lt;br /&gt;
$|V_1| = 1$ とします。この場合、$V_1$ は1つの頂点 $v_0$ を含みます。
このとき、$|N({v_0})| \geq |{v_0}| = 1$ であるため、少なくとも1つの頂点 $w_0 \in V_2$ が $v_0$ に接続されています。この接続により完全マッチングが得られます。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;帰納ステップ&lt;/strong&gt;:&lt;br /&gt;
$|V_1| = k$ のとき、完全マッチングが存在することを仮定します。次に、$|V_1| = k+1$ の場合を示します。&lt;br /&gt;
この場合、帰納的仮定を用いるために2つのケースを考えます。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;ケース (i)&lt;/strong&gt;:&lt;br /&gt;
$V_1$ の任意の部分集合 $A$ ($1 \leq |A| \leq k$) に対して、$A$ の各頂点が少なくとも $|A|+1$ 個の $V_2$ の頂点に接続されていると仮定します。
このとき、任意の $v \in V_1$ を取り除いたグラフ $H&apos; = (V_1 \setminus {v}, V_2 \setminus {w})$ を構成し、帰納法により $H&apos;$ には完全マッチングが存在することを示します。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;ケース (ii)&lt;/strong&gt;:&lt;br /&gt;
ある $j$ ($1 \leq j \leq k$) に対して、$V_1$ の部分集合 $A$ が $V_2$ にちょうど $j$ 個の隣接頂点を持つ場合を考えます。
このとき、帰納的仮定を適用し、$A$ とその隣接頂点を削除したグラフにも完全マッチングが存在することを示します。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;この2つのケースを考慮することで、いずれの場合にも完全マッチングが存在することを示すことができます。&lt;/p&gt;
&lt;p&gt;したがって、$|N(A)| \geq |A|$ が任意の $A \subseteq V_1$ に対して成り立つ場合、$V_1$ から $V_2$ への完全マッチングが存在することが証明されました。&lt;/p&gt;
&lt;h3&gt;10.2.6&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;パーティーにいる人々の集合において、各人が握手をした人数を足し合わせた合計が偶数であることを示しなさい。ただし、自分自身と握手することはないものとします。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;証明&lt;/strong&gt;:&lt;br /&gt;
この問題をグラフとしてモデル化します。パーティーにいる人々をグラフの頂点とし、握手をした2人の間に辺を引きます。このとき、各頂点の次数はその頂点が表す人が握手をした人数に対応します。&lt;/p&gt;
&lt;p&gt;定理1（握手定理）によれば、グラフ内のすべての頂点の次数の和は偶数です（これは $2e$、すなわち辺の数を2倍した値に等しいため）。したがって、握手をした人数の合計も偶数であることが示されます。&lt;/p&gt;
&lt;h3&gt;10.2.18&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;少なくとも2つの頂点を持つ単純グラフでは、次数が同じ頂点が必ず2つ存在することを示しなさい。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;証明&lt;/strong&gt;:&lt;br /&gt;
この問題はセクション6.2の演習42と本質的に同じであり、そこでグラフが「互いに知り合いである」という関係をモデル化しています。&lt;/p&gt;
&lt;p&gt;各人が知っている人数はグラフにおける対応する頂点の次数に等しいと考えます。次のように証明します：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;単純グラフの頂点数を $n$ とします（$n \geq 2$）。&lt;/li&gt;
&lt;li&gt;各頂点の次数の可能な値は $0, 1, \ldots, n-1$ ですが、ある1人が他の全員を知っている場合（次数 $n-1$）、他の誰かが誰も知らない（次数 $0$）という状況は対称性の仮定より不可能です。&lt;/li&gt;
&lt;li&gt;したがって、可能な次数の値は $n$ 個ではなく最大で $n-1$ 個です。&lt;/li&gt;
&lt;li&gt;しかし、頂点の数は $n$ なので、鳩の巣原理（Pigeonhole Principle）によって少なくとも2つの頂点が同じ次数を持つ必要があります。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;これにより、少なくとも2つの頂点が同じ次数を持つことが証明されました。&lt;/p&gt;
&lt;h3&gt;10.2.36&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;$n$ を正の整数とします。完全グラフ $K_n$ の頂点集合の非空部分集合によって誘導される部分グラフが完全グラフであることを示しなさい。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;証明&lt;/strong&gt;:&lt;br /&gt;
完全グラフ $K_n$ では、任意の2頂点が隣接しています。したがって、頂点集合の非空部分集合によって誘導される部分グラフにおいても、その部分集合内の任意の2頂点は隣接しています。これにより、誘導部分グラフが完全グラフであることが示されます。&lt;/p&gt;
&lt;h3&gt;10.2.66&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;もし $G$ が頂点数 $v$、辺数 $e$ を持つ二部単純グラフであるならば、&lt;br /&gt;
$$
e \leq \frac{v^2}{4}
$$
が成り立つ。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;証明&lt;/strong&gt;:&lt;/p&gt;
&lt;p&gt;二部グラフ $G$ を構成する2つの部分のサイズを $k$ と $v - k$ とする。このとき、異なる部分間の各頂点対の間に辺を持つ場合、辺の最大数は&lt;br /&gt;
$$
k(v - k)
$$
で表される。&lt;/p&gt;
&lt;p&gt;関数 $f(k) = k(v - k)$ の最大値を求めると、代数的手法または微分計算により、最大値は $k = \frac{v}{2}$ のときに達成されることが分かる。このとき、&lt;br /&gt;
$$
f(k) = \frac{v^2}{4}
$$
となる。&lt;/p&gt;
&lt;p&gt;したがって、辺の数 $e$ は最大でも $\frac{v^2}{4}$ である。&lt;/p&gt;
&lt;h3&gt;10.4 定理1&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;連結無向グラフの任意の2つの異なる頂点の間には単純な経路(パス)が存在する。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;証明&lt;/strong&gt;:&lt;br /&gt;
$G = (V, E)$ を連結無向グラフとし、$u$ と $v$ を $G$ の2つの異なる頂点とします。$G$ が連結であるため、$u$ と $v$ の間には少なくとも1つの経路が存在します。&lt;/p&gt;
&lt;p&gt;この経路の中で最短のものを選び、その頂点列を $x_0, x_1, \ldots, x_n$ とします。ただし、$x_0 = u$ かつ $x_n = v$ です。この最短経路が単純であることを示します。&lt;/p&gt;
&lt;p&gt;仮にこの経路が単純でないとすると、ある $i$ と $j$ ($0 \leq i &amp;lt; j$) に対して $x_i = x_j$ となるような部分が存在します。
この場合、$x_i$ と $x_j$ の間の頂点列を削除した経路 $x_0, x_1, \ldots, x_{i-1}, x_j, \ldots, x_n$ を構成することで、より短い経路を得ることができます。これは最短経路の仮定に矛盾します。&lt;/p&gt;
&lt;p&gt;したがって、最短経路は単純である必要があります。このことから、任意の2つの異なる頂点の間には単純な経路が存在することが示されました。&lt;/p&gt;
&lt;h3&gt;10.4 定理2（隣接行列と経路(歩道)の関係）&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;グラフ $G$ を、その頂点の順序 $v_1, v_2, \ldots, v_n$ に対応する隣接行列 $A$ を持つグラフとします（有向または無向の辺を持ち、多重辺やループも許容）。
$v_i$ から $v_j$ への長さ $r$ の異なる経路(歩道)の数は、行列 $A^r$ の $(i, j)$ 成分に等しい。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;証明&lt;/strong&gt;:&lt;br /&gt;
この定理を数学的帰納法によって証明します。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;基底の場合 ($r = 1$)&lt;/strong&gt;:&lt;br /&gt;
長さ1の経路とは、単一の辺を意味します。このとき、$v_i$ から $v_j$ への長さ1の経路の数は、隣接行列 $A$ の $(i, j)$ 成分 $a_{ij}$ に対応します。よって、定理は成立します。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;帰納ステップ ($r \to r+1$)&lt;/strong&gt;:&lt;br /&gt;
$r$ に対して、$A^r$ の $(i, j)$ 成分が $v_i$ から $v_j$ への長さ $r$ の経路の数を表していると仮定します（帰納法の仮定）。&lt;/p&gt;
&lt;p&gt;行列 $A^{r+1} = A^r \cdot A$ を考えます。このとき、$A^{r+1}$ の $(i, j)$ 成分は次のように計算されます：
$$
(A^{r+1})&lt;em&gt;{ij} = \sum&lt;/em&gt;{k=1}^n b_{ik} a_{kj},
$$
ただし、$b_{ik}$ は $A^r$ の $(i, k)$ 成分であり、$b_{ik}$ は $v_i$ から $v_k$ への長さ $r$ の経路の数を表しています（帰納法の仮定より）。&lt;/p&gt;
&lt;p&gt;長さ $r+1$ の経路は、長さ $r$ の経路である $v_i$ から $v_k$ への経路に、$v_k$ から $v_j$ への長さ1の辺を追加することで構成されます。
この数をすべての中間頂点 $v_k$ にわたって合計すると、$v_i$ から $v_j$ への長さ $r+1$ の経路の総数が得られます。&lt;/p&gt;
&lt;p&gt;よって、$A^{r+1}$ の $(i, j)$ 成分は、$v_i$ から $v_j$ への長さ $r+1$ の経路の数を表します。&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;以上より、帰納法により定理が示されました。&lt;/p&gt;
&lt;h3&gt;10.4.18&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;強連結成分内の2つの頂点を結ぶ有向経路上に訪れるすべての頂点も、同じ強連結成分に属することを示しなさい。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;証明&lt;/strong&gt;:&lt;br /&gt;
有向経路を $a, b, c, \ldots, z$ とします。ここで、$a$ と $z$ は同じ強連結成分に属していると仮定します。この仮定により、$z$ から $a$ への有向経路が存在します。
この経路を元の経路 $a, b, c, \ldots, z$ に加えることで、閉路が形成されます。&lt;/p&gt;
&lt;p&gt;この閉路を使うことで、元の経路上の任意の頂点から他の任意の頂点に到達することが可能になります。したがって、元の経路上のすべての頂点は同じ強連結成分に属していることが示されます。&lt;/p&gt;
&lt;h3&gt;10.4.28&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;$n$ 頂点を持つ任意の連結グラフは、少なくとも $n-1$ 本の辺を持つことを示しなさい。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;証明&lt;/strong&gt;:&lt;br /&gt;
数学的帰納法を用いて証明します。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;基底の場合 ($n = 1$)&lt;/strong&gt;:&lt;br /&gt;
頂点が1つしかない場合、連結グラフには辺が存在しません。このとき、辺の本数は $n - 1 = 0$ となり、命題が成り立ちます。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;帰納ステップ ($n \to n+1$)&lt;/strong&gt;:&lt;br /&gt;
$n$ 頂点を持つ連結グラフは少なくとも $n-1$ 本の辺を持つと仮定します（帰納法の仮定）。&lt;br /&gt;
$n+1$ 頂点を持ち、$n$ 本未満の辺を持つ連結グラフ $G$ を仮定します。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;グラフ $G$ の頂点の次数の和は $2e$（$e$ は辺の本数）に等しいため、頂点の次数の和は $2n$ 未満です。&lt;/li&gt;
&lt;li&gt;頂点の数は $n+1$ であるため、少なくとも1つの頂点の次数は $2$ 未満でなければなりません。&lt;/li&gt;
&lt;li&gt;グラフが連結であるため、この頂点は孤立しておらず、次数はちょうど $1$ です。この頂点とその接続された辺を削除すると、残りのグラフは $n$ 頂点を持ち、辺の数は $n-1$ 未満になります。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;帰納法の仮定により、これは矛盾を引き起こします。したがって、$n+1$ 頂点を持つ連結グラフは少なくとも $n$ 本の辺を持たなければなりません。&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;以上より、任意の $n$ 頂点を持つ連結グラフは少なくとも $n-1$ 本の辺を持つことが示されました。&lt;/p&gt;
&lt;h3&gt;10.4.29&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;単純グラフ $G = (V, E)$ を考えます。頂点 $V$ 上の関係 $R$ を、頂点の組 $(u, v)$ が $u = v$ または $u$ から $v$ への経路が存在する場合に成立するものと定義します。
このとき、$R$ が同値関係であることを示しなさい。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;証明&lt;/strong&gt;:&lt;br /&gt;
関係 $R$ が同値関係であることを示すために、以下の3つの性質を確認します：反射性、対称性、推移性。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;反射性&lt;/strong&gt;:&lt;br /&gt;
任意の頂点 $u \in V$ に対して、$u$ から自身への経路は存在します（長さ0の経路）。したがって、$(u, u) \in R$ が成立します。よって、$R$ は反射的です。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;対称性&lt;/strong&gt;:&lt;br /&gt;
$(u, v) \in R$ と仮定します。このとき、$u$ から $v$ への経路が存在します。この経路を逆向きにたどることで、$v$ から $u$ への経路が得られます。
したがって、$(v, u) \in R$ が成立します。よって、$R$ は対称的です。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;推移性&lt;/strong&gt;:&lt;br /&gt;
$(u, v) \in R$ および $(v, w) \in R$ と仮定します。
このとき、$u$ から $v$ への経路と $v$ から $w$ への経路を連結することで、$u$ から $w$ への経路が得られます。
したがって、$(u, w) \in R$ が成立します。よって、$R$ は推移的です。&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;以上より、関係 $R$ は反射性、対称性、推移性を満たすため、同値関係であることが示されました。&lt;/p&gt;
&lt;h3&gt;10.4.30&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;任意の単純グラフにおいて、奇数次数を持つ任意の頂点から他の奇数次数の頂点への経路が存在することを示しなさい。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;証明&lt;/strong&gt;:&lt;br /&gt;
$v$ を奇数次数を持つ頂点とします。$H$ を、グラフ $G$ のうち $v$ を含む連結成分とします。この $H$ 自体もグラフであり、ハンドシェイク定理によれば、任意のグラフは奇数次数の頂点を偶数個持つ必要があります。
したがって、$H$ 内には $v$ を除いて少なくとも1つ別の奇数次数の頂点 $w$ が存在します。&lt;/p&gt;
&lt;p&gt;さらに、$H$ は連結であるため、定義より $v$ から $w$ への経路が存在します。以上より、奇数次数の頂点から他の奇数次数の頂点への経路が存在することが証明されました。&lt;/p&gt;
&lt;h3&gt;10.4.35&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;$v$ がカットエッジの端点であるとします。このとき、$v$ がペンダント頂点でない場合に限り、カット頂点であることを示しなさい。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;証明&lt;/strong&gt;:&lt;br /&gt;
ペンダント頂点（次数1の頂点）は明らかにカット頂点ではありません。したがって、カットエッジの端点でカット頂点であるものはペンダント頂点ではありません。&lt;/p&gt;
&lt;p&gt;カットエッジを削除すると、元のグラフの連結成分の数よりも多くの連結成分を持つグラフが生成されます。もしカットエッジの端点がペンダント頂点でない場合、その端点を含む連結成分には他の頂点が存在します。このとき、その頂点 $v$ とその頂点に接続するすべての辺を削除すると、元のカットエッジを含むすべての辺が削除されるため、元のグラフの連結成分の数よりもさらに多くの連結成分を持つグラフが生成されます。&lt;/p&gt;
&lt;p&gt;したがって、カットエッジの端点でペンダント頂点でないものはカット頂点です。&lt;/p&gt;
&lt;h3&gt;10.4.36&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;連結単純グラフ $G$ において、ある頂点 $c$ がカット頂点であることと、$c$ とは異なる頂点 $u$ と $v$ が存在し、
$u$ と $v$ のすべての経路が $c$ を通ることが同値であることを示しなさい。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;証明&lt;/strong&gt;:&lt;br /&gt;
まず、$c$ がカット頂点である場合を考えます。この場合、$c$ を削除すると、グラフの連結成分の数が増加します。
したがって、$c$ を削除した後、異なる連結成分に属する2つの頂点 $u$ と $v$ が存在します。このような $u$ と $v$ の間のすべての経路は $c$ を通る必要があります。&lt;/p&gt;
&lt;p&gt;逆に、$u$ と $v$ のすべての経路が $c$ を通る場合を考えます。このとき、$c$ を削除すると $u$ と $v$ は異なる連結成分に属することになります。
したがって、$c$ を削除すると少なくとも2つの連結成分が生成されるため、$c$ はカット頂点です。&lt;/p&gt;
&lt;p&gt;これにより、カット頂点の条件と経路条件が同値であることが示されました。&lt;/p&gt;
&lt;h3&gt;10.4.37&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;少なくとも2つの頂点を持つ単純グラフには、カット頂点でない頂点が少なくとも2つ存在することを示しなさい。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;証明&lt;/strong&gt;:&lt;br /&gt;
仮に、カット頂点でない頂点が高々1つしか存在しないと仮定します。
この場合、グラフ $G$ の頂点 $u$ と $v$ の距離（最短経路の長さ）を $d(u, v)$ とし、$d(u, v)$ が最大となる2つの頂点 $s$ と $t$ を取ります。
このとき、$s$ または $t$（またはその両方）はカット頂点である必要があります。&lt;/p&gt;
&lt;p&gt;$s$ をカット頂点と仮定し、$s$ を削除して得られるグラフの連結成分の1つに属する頂点 $w$ を考えます。
この場合、$w$ から $t$ へのすべての経路は $s$ を通らなければなりません。これにより、$d(w, t) &amp;gt; d(s, t)$ となりますが、これは $d(s, t)$ が最大であるという仮定に矛盾します。&lt;/p&gt;
&lt;p&gt;したがって、カット頂点でない頂点は少なくとも2つ存在しなければなりません。&lt;/p&gt;
&lt;h3&gt;10.4.38&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;単純グラフにおいて、ある辺がカットエッジであることと、その辺が単純な閉路の一部でないことが同値であることを示しなさい。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;証明&lt;/strong&gt;:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;カットエッジである場合&lt;/strong&gt;:&lt;br /&gt;
$e = {u, v}$ をカットエッジと仮定します。この場合、$e$ を削除すると、$u$ と $v$ の間に経路が存在しなくなり、グラフの連結性が失われます。
したがって、$u$ と $v$ を結ぶ経路は $e$ を必ず含む必要があります。&lt;br /&gt;
$e$ を含む任意の閉路において、$u$ から $v$ への経路と $v$ から $u$ への経路の両方が $e$ を使用しなければならないため、$e$ が2回出現することになります。したがって、この閉路は単純ではありません。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;単純な閉路の一部でない場合&lt;/strong&gt;:&lt;br /&gt;
$e = {u, v}$ が単純な閉路の一部でないと仮定します。この場合、$e$ を削除しても、$u$ と $v$ の間には少なくとも1つの経路が残ります。
このため、$e$ を削除してもグラフの連結性は失われません。したがって、$e$ はカットエッジではありません。&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;これらの結果より、ある辺がカットエッジであることと、その辺が単純な閉路の一部でないことが同値であることが示されました。&lt;/p&gt;
&lt;h3&gt;10.4.45&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;頂点数が $n$ の単純グラフ $G$ が $(n-1)(n-2)/2$ 本を超える辺を持つ場合、$G$ が連結であることを示しなさい。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;証明&lt;/strong&gt;:&lt;br /&gt;
仮定として、グラフ $G$ が連結でないとします。この場合、$G$ は複数の連結成分を持ち、各成分の頂点数を考慮して、最大の辺の数を計算します。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;連結成分の頂点数を分割&lt;/strong&gt;:&lt;br /&gt;
$G$ の1つの連結成分が $k$ 頂点を持つと仮定します（$1 \leq k \leq n-1$）。残りの $n-k$ 頂点が別の成分を形成するとします。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;各成分での最大辺数&lt;/strong&gt;:&lt;br /&gt;
頂点数 $k$ の完全グラフの辺数は $\binom{k}{2} = k(k-1)/2$ であり、
同様に $n-k$ 頂点の完全グラフの辺数は $\binom{n-k}{2} = (n-k)(n-k-1)/2$ です。&lt;/p&gt;
&lt;p&gt;$G$ が持つことができる最大の辺の総数は次のようになります：
$$
E_{\max} = \frac{k(k-1)}{2} + \frac{(n-k)(n-k-1)}{2}.
$$&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;2次関数として解析&lt;/strong&gt;:&lt;br /&gt;
上記の式を簡単化すると次のようになります：
$$
f(k) = \frac{k^2 - k + (n-k)^2 - (n-k)}{2} = \frac{k^2 - nk + n^2 - n}{2}.
$$
この式は $k$ に関する2次関数です。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;最大値の評価&lt;/strong&gt;:&lt;br /&gt;
この関数は $k = 1$ または $k = n-1$ で最大値をとります。これらの場合、$f(k)$ は次のようになります：
$$
f(1) = \frac{(n-1)(n-2)}{2}.
$$
よって、連結でないグラフが持つ辺の数は高々 $(n-1)(n-2)/2$ です。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;結論&lt;/strong&gt;:&lt;br /&gt;
$G$ が $(n-1)(n-2)/2$ 本を超える辺を持つ場合、$G$ は必ず連結でなければなりません。&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;これにより命題が証明されました。&lt;/p&gt;
&lt;h3&gt;10.4.51&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;もし $G$ が連結グラフであるならば、$G$ が完全グラフでない場合に限り、頂点を削除することで $G$ を非連結にすることができる。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;証明&lt;/strong&gt;:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;（必要条件）&lt;/strong&gt;&lt;br /&gt;
もし $G$ が完全グラフであるならば、頂点を1つずつ削除しても、各ステップで残るグラフは依然として完全グラフである。このため、どの時点でも $G$ は非連結にならない。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;（十分条件）&lt;/strong&gt;&lt;br /&gt;
逆に、もし $G$ から辺 $uv$ が欠けている場合、$G$ は完全グラフではない。このとき、$u$ と $v$ を除く全ての頂点を削除すると、残るグラフは $u$ と $v$ のみから構成され、両者の間に辺が存在しないため、非連結になる。&lt;/p&gt;
&lt;p&gt;したがって、$G$ が完全グラフでない場合、頂点を削除して $G$ を非連結にすることが可能である。&lt;/p&gt;
&lt;h3&gt;10.4.55&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;グラフ $G$ において、
$$
\kappa(G) \leq \lambda(G)
$$
が成り立つことを示せ。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;証明&lt;/strong&gt;:&lt;/p&gt;
&lt;p&gt;$G$ を $n$ 個の頂点を持つグラフとする。このとき、$\kappa(G) \leq n - 1$ である。&lt;br /&gt;
$C$ を $G$ の最小の辺カットとし、頂点集合 $S$ を $G$ の頂点集合 $V$ の補集合 $S&apos; = V - S$ から切り離す非空の部分集合とする。&lt;/p&gt;
&lt;p&gt;もし任意の $x \in S$ と $y \in S&apos;$ に対して $xy$ が $G$ の辺であるならば、カット集合 $C$ のサイズは $|S||S&apos;|$ となる。
この値は少なくとも $n - 1$ であるため、$\kappa(G) \leq \lambda(G)$ が成り立つ。&lt;/p&gt;
&lt;p&gt;そうでない場合、$x \in S$ と $y \in S&apos;$ が非隣接頂点であると仮定する。このとき、集合 $T$ を以下のように定義する：$S$ の全ての隣接頂点と $S&apos;$ 内の $x$ の隣接頂点から構成される。
さらに、$x$ を除く $S$ の頂点全てを追加する。&lt;/p&gt;
&lt;p&gt;集合 $T$ は $x$ と $y$ を分離する頂点カットであり、このカットが $S$ と $S&apos;$ を分離する。
今度は、$T \cap S$ から $S&apos;$ への辺、および $T \cap S$ の各頂点から $S&apos;$ への1つの辺を考える。このような辺の集合は $|T|$ 個の辺を形成する。&lt;/p&gt;
&lt;p&gt;このことから、次が成り立つ：&lt;br /&gt;
$$
\lambda(G) = |C| \geq |T| \geq \kappa(G).
$$&lt;/p&gt;
&lt;p&gt;したがって、$\kappa(G) \leq \lambda(G)$ が示された。&lt;/p&gt;
&lt;h3&gt;10.4.59&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;単純グラフ $G$ において、頂点 $u$ と $v$ の間に同じ辺の集合を含まない2つの単純パス $P_1$ と $P_2$ が存在すると仮定する。このとき、$G$ に単純閉路（サイクル）が存在することを示せ。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;証明&lt;/strong&gt;:&lt;/p&gt;
&lt;p&gt;単純パス $P_1$ と $P_2$ をそれぞれ&lt;br /&gt;
$$
u = x_0, x_1, \ldots, x_n = v
$$
および&lt;br /&gt;
$$
u = y_0, y_1, \ldots, y_m = v
$$
とする。これらのパスは同じ頂点 $u$ から始まるが、同じ辺の集合を含まないと仮定されているため、いずれかの時点で分岐する必要がある。&lt;/p&gt;
&lt;p&gt;分岐が $P_1$ または $P_2$ の終了後にのみ発生する場合、残りのもう一方のパスが $v$ から $v$ への単純閉路を形成する。一方、次のように仮定できる：&lt;br /&gt;
$$
x_0 = y_0, x_1 = y_1, \ldots, x_i = y_i,
$$
だが&lt;br /&gt;
$$
x_{i+1} \neq y_{i+1}.
$$&lt;/p&gt;
&lt;p&gt;単純閉路を構成するために、パス $y_i, y_{i+1}, y_{i+2}, \ldots$ をたどり、最初に $P_1$ 上の頂点に再び遭遇するまで進む（これは $y_{i+1}$ 以上、遅くとも $y_m$ までには発生する）。
その後、$P_1$ 上を前方または後方に進むことで、再び $x_i$ に戻る。このプロセスにより閉路が形成される。&lt;/p&gt;
&lt;p&gt;$x_i = y_i$ であるため、確かに閉路が形成される。この閉路は単純である。なぜなら、$P_1$ と $P_2$ が単純であるという仮定より、$x_k$ や $y_l$ の間で辺の重複がないことが保証される。
また、$P_2$ から $P_1$ に切り替えた時点で、既に使用した $y_l$ の辺が $x_k$ のいずれかと等しくなることはないためである。&lt;/p&gt;
&lt;p&gt;したがって、$G$ には単純閉路が存在することが示された。&lt;/p&gt;
&lt;h3&gt;10.4.63&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;単純グラフ $G$ が二部グラフであるのは、奇数本の辺を持つ閉路を持たない場合、かつその場合に限ることを示しなさい。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;証明&lt;/strong&gt;:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;$G$ が二部グラフである場合&lt;/strong&gt;:&lt;br /&gt;
$G$ を二部グラフと仮定し、その二部集合を $A$ と $B$ とします。二部グラフでは、すべての経路は $A$ と $B$ の頂点を交互に通ります。
したがって、経路の長さが奇数であれば $A$ で始まり $B$ で終わり、長さが偶数であれば $A$ で始まり再び $A$ で終わります。&lt;/p&gt;
&lt;p&gt;閉路は開始頂点と終了頂点が同一であるため、閉路の長さは必ず偶数になります。したがって、二部グラフ $G$ は奇数本の辺を持つ閉路を持ちません。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;$G$ が奇数本の辺を持つ閉路を持たない場合&lt;/strong&gt;:&lt;br /&gt;
$G$ が奇数本の辺を持つ閉路を持たないと仮定します。この場合、次の手順で $G$ が二部グラフであることを示します：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;グラフが連結でない場合、各連結成分について個別に検討すればよいので、連結グラフを仮定してよい。&lt;/li&gt;
&lt;li&gt;$G$ の任意の頂点 $v$ を選び、$A$ を $v$ から奇数長の経路で到達可能な頂点集合、$B$ を $v$ から偶数長の経路で到達可能な頂点集合と定義します。&lt;/li&gt;
&lt;li&gt;$G$ の連結性から、すべての頂点は $A$ または $B$ のいずれかに属します。&lt;/li&gt;
&lt;li&gt;もしある頂点が $A$ と $B$ の両方に属するならば、ある奇数長の経路と偶数長の経路を結合して奇数長の閉路を構成することができ、仮定に矛盾します。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;さらに、任意の辺 $xy$ について、$x \in A$ の場合、$x$ から $y$ への経路は偶数長となり、$y$ は $B$ に属します。
同様に、$x \in B$ の場合は $y \in A$ です。したがって、すべての辺が $A$ と $B$ の異なる部分に端点を持ちます。&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;以上により、$G$ は二部グラフであることが示されました。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;結論&lt;/strong&gt;:&lt;br /&gt;
$G$ が二部グラフであることと、奇数本の辺を持つ閉路を持たないことは同値です。&lt;/p&gt;
&lt;h3&gt;10.5 定理1（オイラー閉路の十分かつ必要条件）&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;少なくとも2つの頂点を持つ連結多重グラフにおいて、すべての頂点の次数が偶数である場合に限り、オイラー閉路が存在する。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;必要条件の証明&lt;/strong&gt;:&lt;br /&gt;
オイラー閉路が存在する連結多重グラフを考えます。この閉路は同じ頂点 $a$ で始まり終わる閉路です。閉路が始点である頂点 $a$ に到達するとき、この頂点の次数は以下のように寄与します：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;最初の到達時、次数に1を加算。&lt;/li&gt;
&lt;li&gt;最後の離脱時、次数に1を加算。&lt;/li&gt;
&lt;li&gt;その他の通過時には、到達時と離脱時の2回ずつ加算。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;したがって、頂点 $a$ の次数は常に偶数です。同様に、他の頂点も閉路において到達と離脱がセットで起こるため、すべての頂点の次数が偶数である必要があります。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;十分条件の証明&lt;/strong&gt;:&lt;br /&gt;
連結多重グラフ $G$ のすべての頂点の次数が偶数であると仮定します。このとき、次のようにオイラー閉路を構成します：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;任意の頂点 $a$ を始点として、利用可能な辺を1本ずつ選びながら単純な閉路を作ります。この閉路は有限個の辺しかないため、最終的には $a$ に戻ります。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;この閉路を取り除いた残りのグラフを $H$ とします。$H$ は以下の性質を持ちます：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;$H$ のすべての頂点も偶数の次数を持つ。&lt;/li&gt;
&lt;li&gt;$H$ が連結でない場合、各連結成分について同様の閉路を構成できます。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;残りの閉路を元の閉路に統合します。これを全ての辺が使い切られるまで繰り返すと、オイラー閉路が完成します。&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;結論&lt;/strong&gt;:&lt;br /&gt;
連結多重グラフにおいて、すべての頂点の次数が偶数であれば、オイラー閉路を構成できることが示されました。&lt;/p&gt;
&lt;h3&gt;10.5 定理2（オイラーパスの十分かつ必要条件）&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;連結多重グラフがオイラーパス（ただしオイラー閉路ではない）を持つのは、奇数次数の頂点がちょうど2つ存在する場合に限る。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;必要条件の証明&lt;/strong&gt;:&lt;br /&gt;
連結多重グラフ $G$ がオイラーパスを持つと仮定します。このオイラーパスが $a$ から $b$ に至るものとします。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;オイラーパスの最初の辺は $a$ の次数に1を加算します。&lt;/li&gt;
&lt;li&gt;路が $a$ を通過するたびに、次数に2を加算します。&lt;/li&gt;
&lt;li&gt;最後の辺は $b$ の次数に1を加算します。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;したがって、$a$ と $b$ の次数は奇数になります。それ以外の頂点はすべて通過するたびに次数に2を加算するため、偶数の次数を持ちます。したがって、奇数次数の頂点はちょうど2つである必要があります。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;十分条件の証明&lt;/strong&gt;:&lt;br /&gt;
次に、奇数次数の頂点がちょうど2つである連結多重グラフ $G$ を考えます。この頂点を $a$ と $b$ とします。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;$G$ に仮想的な辺 ${a, b}$ を追加して新しいグラフ $G&apos;$ を構成します。&lt;/li&gt;
&lt;li&gt;$G&apos;$ のすべての頂点の次数が偶数になるため、定理1（オイラー閉路の十分かつ必要条件）により、$G&apos;$ にはオイラー閉路が存在します。&lt;/li&gt;
&lt;li&gt;$G&apos;$ のこのオイラー閉路から仮想の辺 ${a, b}$ を削除すると、元のグラフ $G$ における $a$ から $b$ へのオイラーパスが得られます。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;これにより、奇数次数の頂点がちょうど2つ存在する場合、$G$ はオイラーパスを持つことが示されました。&lt;/p&gt;
&lt;h3&gt;10.5.16&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;孤立した頂点を持たない有向多重グラフがオイラー閉路を持つのは、そのグラフが弱連結であり、各頂点の入次数と出次数が等しい場合、かつその場合に限ることを示しなさい。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;証明&lt;/strong&gt;:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;必要条件の証明&lt;/strong&gt;:&lt;br /&gt;
有向多重グラフにオイラー閉路が存在すると仮定します。このオイラー閉路は各頂点を通り、最終的に出発点に戻ります。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;弱連結性&lt;/strong&gt;:&lt;br /&gt;
オイラー閉路が存在するためには、すべての頂点が閉路に含まれる必要があります。このため、グラフは弱連結である必要があります。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;入次数と出次数の一致&lt;/strong&gt;:&lt;br /&gt;
オイラー閉路が各頂点を通過するとき、辺が頂点に「入る」たびに入次数が1加算され、「出る」たびに出次数が1加算されます。最終的に、各頂点の入次数と出次数は同じになります。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;十分条件の証明&lt;/strong&gt;:&lt;br /&gt;
グラフが弱連結であり、各頂点の入次数と出次数が等しいと仮定します。この場合、次の手順でオイラー閉路を構成できます。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;オイラー閉路の十分条件を示した定理1に基づき、有向グラフにおけるオイラー閉路を構築します。出次数が0になるまで各頂点から出発し、すべての辺を使用する単純な閉路を作成します。&lt;/li&gt;
&lt;li&gt;すべての辺が使用されるまで、残りの部分グラフに対して同様の手順を適用し、それぞれの閉路を結合して1つのオイラー閉路を完成させます。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;結論&lt;/strong&gt;:&lt;br /&gt;
孤立した頂点を持たない有向多重グラフがオイラー閉路を持つのは、グラフが弱連結であり、かつ各頂点の入次数と出次数が等しい場合に限ることが示されました。&lt;/p&gt;
&lt;h3&gt;10.5.17&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;孤立した頂点を持たない有向多重グラフがオイラーパスを持つがオイラー閉路を持たないのは、そのグラフが弱連結であり、すべての頂点の入次数と出次数が等しいが、次の2つの頂点を例外とする場合に限る：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;1つは出次数が入次数より1大きい頂点（開始頂点）。&lt;/li&gt;
&lt;li&gt;もう1つは入次数が出次数より1大きい頂点（終了頂点）。&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;証明&lt;/strong&gt;:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;必要条件の証明&lt;/strong&gt;:&lt;br /&gt;
有向多重グラフがオイラーパスを持つと仮定します。このとき、次の性質が成立します：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;入次数と出次数の関係&lt;/strong&gt;:&lt;br /&gt;
オイラーパスでは、途中で訪れるすべての頂点は、入次数と出次数が等しくなります。なぜなら、訪問時に1本の辺で頂点に「入り」、次の辺で頂点から「出る」ためです。&lt;br /&gt;
ただし、開始頂点では出次数が入次数より1多く、終了頂点では入次数が出次数より1多くなります。これにより、オイラーパスの出発と終了が確定されます。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;弱連結性&lt;/strong&gt;:&lt;br /&gt;
オイラーパスはすべての辺を使用するため、グラフ全体が1つの連結成分に属します。このため、グラフは少なくとも弱連結でなければなりません。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;十分条件の証明&lt;/strong&gt;:&lt;br /&gt;
グラフが弱連結であり、すべての頂点で入次数と出次数が等しいが、以下の2つの例外があると仮定します：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;頂点 $u$: 出次数が入次数より1大きい（開始頂点）。&lt;/li&gt;
&lt;li&gt;頂点 $v$: 入次数が出次数より1大きい（終了頂点）。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;このとき、次のようにオイラーパスを構築できます：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;頂点 $u$ から頂点 $v$ に仮想的な辺を追加した新しいグラフを構成します。この新しいグラフでは、すべての頂点で入次数と出次数が等しくなります。&lt;/li&gt;
&lt;li&gt;新しいグラフは弱連結であるため、定理16により、このグラフにはオイラー閉路が存在します。&lt;/li&gt;
&lt;li&gt;このオイラー閉路から仮想的に追加した辺を削除すると、元のグラフにおける $u$ から $v$ へのオイラーパスが得られます。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;結論&lt;/strong&gt;:&lt;br /&gt;
孤立した頂点を持たない有向多重グラフがオイラーパスを持つがオイラー閉路を持たないのは、そのグラフが弱連結であり、すべての頂点で入次数と出次数が等しいが、1つの頂点で出次数が入次数より1大きく、もう1つの頂点で入次数が出次数より1大きい場合に限ることが示されました。&lt;/p&gt;
&lt;h3&gt;10.6 定理1（ダイクストラのアルゴリズム）&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;ダイクストラのアルゴリズムは、連結な単純無向重み付きグラフにおける2つの頂点間の最短経路の長さを見つける。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;証明&lt;/strong&gt;:&lt;br /&gt;
数学的帰納法を用いてダイクストラのアルゴリズムが正しいことを示します。各反復における帰納法の仮定は以下の通りです：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;帰納法の仮定&lt;/strong&gt;:&lt;br /&gt;
$k$ 回目の反復において以下が成り立つと仮定する：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;(i) $S$ に含まれるすべての頂点のラベルは、頂点 $a$ からその頂点への最短経路の長さである。&lt;/li&gt;
&lt;li&gt;(ii) $S$ に含まれないすべての頂点のラベルは、頂点 $a$ からその頂点への最短経路の長さ（ただし、この経路は $S$ の頂点のみを含む）である。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;基底の場合 ($k = 0$)&lt;/strong&gt;:&lt;br /&gt;
初期状態では $S = \emptyset$ であり、すべての頂点のラベルは無限大 ($\infty$) に設定されます。ただし、始点 $a$ のラベルは 0 です。これにより、基底の場合が成り立つことが確認されます。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;帰納ステップ ($k \to k+1$)&lt;/strong&gt;:&lt;br /&gt;
帰納法の仮定を用いて、$k+1$ 回目の反復で仮定が成り立つことを示します：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;頂点 $v$ を、ラベルが最小の頂点として $S$ に追加します。&lt;/li&gt;
&lt;li&gt;$v$ のラベルは、$a$ から $v$ への最短経路の長さである必要があります。もしそうでなければ、ラベルが小さい他の頂点が存在することになり矛盾します。&lt;/li&gt;
&lt;li&gt;$S$ に含まれない頂点については、そのラベルが更新され、次のように計算されます：
$$
L_{k+1}(u) = \min{L_k(u), L_k(v) + w(v, u)},
$$
ここで、$w(v, u)$ は頂点 $v$ と $u$ を結ぶ辺の重みです。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;結論&lt;/strong&gt;:&lt;br /&gt;
帰納法により、すべての反復が終了した時点で、各頂点のラベルは頂点 $a$ からその頂点への最短経路の長さとなります。&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;10.6 定理2（ダイクストラのアルゴリズムの計算量）&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;ダイクストラのアルゴリズムは、頂点数 $n$ の連結な単純無向重み付きグラフにおいて、2つの頂点間の最短経路の長さを見つけるために、$O(n^2)$ の操作（加算および比較）を使用する。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;証明&lt;/strong&gt;:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;反復回数&lt;/strong&gt;:&lt;br /&gt;
ダイクストラのアルゴリズムは、グラフの頂点数 $n$ に基づき、最大で $n-1$ 回の反復を行います。各反復で、1つの頂点が集合 $S_k$ に追加されるためです。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;各反復での操作数&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;最小ラベルを持つ頂点を特定するために、最大で $n-1$ 回の比較が必要です（$S_k$ に含まれない頂点の中から選択するため）。&lt;/li&gt;
&lt;li&gt;$S_k$ に含まれない各頂点のラベルを更新するためには、加算および比較の操作が必要です。これは各頂点に対して1回行われ、最大 $n-1$ 個の頂点について実行されます。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;したがって、各反復で必要な操作の総数は次のようになります：
$$
2(n-1)
$$
これは最大の加算および比較回数を表します。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;全体の計算量&lt;/strong&gt;:&lt;br /&gt;
最大 $n-1$ 回の反復が行われるため、アルゴリズム全体で必要な操作の合計は次の通りです：
$$
(n-1) \times 2(n-1) = O(n^2)
$$&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;10.7 定理1 （オイラーの公式）&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;$G$ を $e$ 本の辺と $v$ 個の頂点を持つ連結平面単純グラフとします。$G$ の平面表現における領域の数を $r$ とすると、以下の式が成り立つ：
$$
r = e - v + 2
$$&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;証明&lt;/strong&gt;:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;平面表現の構築&lt;/strong&gt;:&lt;br /&gt;
$G$ の平面表現を作るために、次の手順で部分グラフの列 $G_1, G_2, \dots, G_e = G$ を構築します：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;任意の1つの辺を選択して $G_1$ を作成します。&lt;/li&gt;
&lt;li&gt;$G_n$ を、既存の部分グラフ $G_{n-1}$ に新しい辺を追加して作成します。このとき、新しい辺は $G_{n-1}$ に既に存在する頂点に接続される必要があります。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;各段階で、領域の数 $r_n$、辺の数 $e_n$、および頂点の数 $v_n$ を記録します。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;基底の場合&lt;/strong&gt;:&lt;br /&gt;
最初の部分グラフ $G_1$ は、1本の辺と2つの頂点を持ち、領域の数は1です。式
$$
r_1 = e_1 - v_1 + 2
$$
は
$$
1 = 1 - 2 + 2
$$
を満たすため、基底の場合が成立します。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;帰納法の仮定&lt;/strong&gt;:&lt;br /&gt;
$G_k$ に対して、以下の式が成り立つと仮定します：
$$
r_k = e_k - v_k + 2
$$&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;帰納ステップ&lt;/strong&gt;:&lt;br /&gt;
$G_{k+1}$ を $G_k$ に新しい辺 ${a_{k+1}, b_{k+1}}$ を追加して得られるグラフとします。この場合、次の2つの状況を考慮します：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;ケース1&lt;/strong&gt;: 両端点 $a_{k+1}$ と $b_{k+1}$ が $G_k$ に既に存在している場合：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;この新しい辺は既存の領域を2つに分割するため、領域の数が1つ増えます（$r_{k+1} = r_k + 1$）。&lt;/li&gt;
&lt;li&gt;辺の数は1つ増えます（$e_{k+1} = e_k + 1$）。&lt;/li&gt;
&lt;li&gt;頂点の数は変わりません（$v_{k+1} = v_k$）。&lt;/li&gt;
&lt;li&gt;これにより、次の式が成立します：
$$
r_{k+1} = e_{k+1} - v_{k+1} + 2
$$&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;ケース2&lt;/strong&gt;: $a_{k+1}$ または $b_{k+1}$ のどちらかが $G_k$ に存在しない場合：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;新しい辺の追加により領域の数は変わりません（$r_{k+1} = r_k$）。&lt;/li&gt;
&lt;li&gt;辺の数が1つ増え（$e_{k+1} = e_k + 1$）、頂点の数も1つ増えます（$v_{k+1} = v_k + 1$）。&lt;/li&gt;
&lt;li&gt;これにより、次の式が成立します：
$$
r_{k+1} = e_{k+1} - v_{k+1} + 2
$$&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;結論&lt;/strong&gt;:&lt;br /&gt;
帰納法により、すべての $G_n$ において $r_n = e_n - v_n + 2$ が成り立つことが示されました。特に、元のグラフ $G$ においてもこの式が成立します。&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;10.7 系1&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;$G$ を $e$ 本の辺と $v$ 個の頂点を持つ連結平面単純グラフとする。$v \geq 3$ のとき、次が成り立つ：
$$
e \leq 3v - 6
$$&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;証明&lt;/strong&gt;:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;領域の次数の性質&lt;/strong&gt;:&lt;br /&gt;
平面上に描かれた連結平面単純グラフは、平面を $r$ 個の領域に分割します。各領域の次数（その領域の境界に含まれる辺の数）は少なくとも3以上です。これは以下の理由によります：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;単純グラフでは、多重辺や自己ループが存在しないため、次数2や1の領域を形成することはありません。&lt;/li&gt;
&lt;li&gt;特に、無限領域も少なくとも3以上の次数を持ちます。これは、グラフに少なくとも3つの頂点が含まれているためです。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;辺の数と領域の次数の関係&lt;/strong&gt;:&lt;br /&gt;
グラフのすべての辺は領域の境界に2回現れるため、領域の次数の総和は次のように表されます：
$$
2e = \sum_{\text{faces } R} \deg(R)
$$
また、各領域の次数が3以上であるため：
$$
\sum_{\text{faces } R} \deg(R) \geq 3r
$$
よって次の不等式が得られます：
$$
2e \geq 3r
$$&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;オイラーの公式の利用&lt;/strong&gt;:&lt;br /&gt;
オイラーの公式 $r = e - v + 2$ を用いると、$r$ を $e$ と $v$ によって次のように表せます：
$$
r = e - v + 2
$$
これを $2e \geq 3r$ に代入すると：
$$
\begin{aligned}
2e &amp;amp;\geq 3(e - v + 2) \
&amp;amp;= 3e - 3v + 6
\end{aligned}
$$
展開して整理すると：
$$
e \leq 3v - 6
$$&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;10.7 系2&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;$G$ を連結な平面単純グラフとする。このとき、$G$ には次数が5以下の頂点が存在する。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;証明&lt;/strong&gt;:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;場合分け&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;$G$ の頂点数が1または2の場合、全ての頂点の次数は5以下であるため、命題が成り立ちます。&lt;/li&gt;
&lt;li&gt;$G$ の頂点数が3以上の場合を考えます。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;手がかり&lt;/strong&gt;:&lt;br /&gt;
コロラリー1より、辺の本数 $e$ と頂点数 $v$ の間には次の不等式が成り立ちます：
$$
e \leq 3v - 6
$$
これに基づき、辺数を次数の和を用いて表すと、ハンドシェーキング定理より次の式が得られます：
$$
2e = \sum_{v \in V} \deg(v)
$$&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;矛盾の導出&lt;/strong&gt;:&lt;br /&gt;
すべての頂点の次数が6以上であると仮定すると、次数の総和は次のようになります：
$$
\sum_{v \in V} \deg(v) \geq 6v
$$
一方で、コロラリー1の不等式 $e \leq 3v - 6$ を用いると、
$$
2e \leq 6v - 12
$$
これらを比較すると、
$$
6v \leq 6v - 12
$$
となり、明らかに矛盾します。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;結論&lt;/strong&gt;:&lt;br /&gt;
矛盾が生じたことから、すべての頂点が次数6以上であることはあり得ません。したがって、次数が5以下の頂点が少なくとも1つ存在することが示されました。&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;10.7 系3&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;$G$ を $e$ 本の辺と $v$ 個の頂点を持つ連結平面単純グラフとし、$v \geq 3$ であり、長さ3の閉路を持たないとする。このとき次が成り立つ：
$$
e \leq 2v - 4
$$&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;証明&lt;/strong&gt;:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;領域の次数に関する条件&lt;/strong&gt;:&lt;br /&gt;
長さ3の閉路がないため、すべての領域の次数は少なくとも4以上です。これに基づき、領域の次数の合計は次の不等式を満たします：
$$
2e = \sum_{\text{faces } R} \deg(R) \geq 4r
$$
ここで $r$ は領域の数を表します。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;領域数 $r$ の式&lt;/strong&gt;:&lt;br /&gt;
オイラーの公式 $r = e - v + 2$ を用いると、次の式を得ます：
$$
r \leq \frac{e}{2}
$$
したがって、オイラーの公式をこの不等式に代入すると：
$$
e - v + 2 \leq \frac{e}{2}
$$&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;整理&lt;/strong&gt;:&lt;br /&gt;
上記の不等式を整理すると：
$$
\begin{aligned}
2(e - v + 2) &amp;amp;\leq e \
2e - 2v + 4 &amp;amp;\leq e \
e &amp;amp;\leq 2v - 4
\end{aligned}
$$&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;10.7.17&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;$e$ 本の辺と $v$ 個の頂点を持つ連結平面単純グラフがあり、長さ4以下の単純閉路を持たないとします。さらに $v \geq 4$ のとき、次が成り立つ：
$$
e \leq \frac{5}{3}v - \frac{10}{3}
$$&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;証明&lt;/strong&gt;:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;領域の次数の性質&lt;/strong&gt;:&lt;br /&gt;
グラフには長さ4以下の単純閉路が存在しないため、すべての領域の次数は少なくとも5以上です。この条件に基づき、領域の次数の合計は次のように評価できます：
$$
2e = \sum_{\text{faces } R} \deg(R) \geq 5r
$$
ここで、$r$ は領域の数を表します。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;オイラーの公式の利用&lt;/strong&gt;:&lt;br /&gt;
オイラーの公式 $r = e - v + 2$ を用いると：
$$
2e \geq 5r
$$
を次のように変形できます：
$$
\begin{aligned}
2e &amp;amp;\geq 5(e - v + 2) \
&amp;amp;= 5e - 5v + 10 \
3e &amp;amp;\leq 5v - 10 \
e &amp;amp;\leq \frac{5}{3}v - \frac{10}{3}
\end{aligned}
$$&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
</content:encoded></item><item><title>コンピュアーキまとめ</title><link>https://www.shiinayane.com/ja/posts/computer-architecture/</link><guid isPermaLink="true">https://www.shiinayane.com/ja/posts/computer-architecture/</guid><pubDate>Sun, 26 Jan 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;ここで、教科書に暗記必要である知識をまとめて書きました。&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;2. データの流れと制御の流れ&lt;/h2&gt;
&lt;h3&gt;第2章まとめ&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;主記憶装置&lt;/strong&gt;: アドレスによって命令の読書きを行う大容量のメモリである。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;ROM&lt;/strong&gt;: 読み出し専用メモリで、マスクROM、ヒューズROMとEPROMに分類される。
フラッシュメモリはEPROMの一種である。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;RAM&lt;/strong&gt;: 読み書きのできるメモリである。低速大容量のDRAMと高速小容量のSRAMがある。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;命令&lt;/strong&gt;: コンピュータを制御する源となるもので、2進数のデータとして表現され、命令メモリに格納されている。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;命令の種類&lt;/strong&gt;: 算術論理演算命令、メモリ操作命令、分岐命令に分類される。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;命令実行サイクル&lt;/strong&gt;: フェッチ、デコード、実行、結果の格納の四つの動作から成る。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;シーケンサ&lt;/strong&gt;: シーケンサは、命令アドレスの生成回路である。
次の命令アドレスを決める機構で、プログラムカウンタと付加回路から成る。&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;命令実行サイクルのサンプル&lt;/h3&gt;
&lt;p&gt;以下は算術論理演算命令の実行サイクルである。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;命令フェッチ&lt;/strong&gt;: 命令メモリから図2.7 (a) の形式の命令を読み込む。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;命令デコード&lt;/strong&gt;: 命令デコードでALUの制御信号を生成する。同時に、レジスタファイルからALUへの入力となる二つのレジスタの値を読み出す。
レジスタアドレスは、命令の2番目と3番目のフィールドに格納されている。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;演算実行&lt;/strong&gt;: ALUがデコーダで指定された演算 ($+$) を実行する。結果の選択信号をALUからの出力を選択するようにセットする。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;結果の格納&lt;/strong&gt;: レジスタファイルに実行結果が格納される。結果が入るレジスタアドレスは、命令の4番目のフィールドに格納されている。&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;hr /&gt;
&lt;h2&gt;3. 命令セットアーキテクチャ&lt;/h2&gt;
&lt;h3&gt;第3章まとめ&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;命令セット&lt;/strong&gt;: コンピュータのすべての命令の集まりである。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;命令の表現形式&lt;/strong&gt;: 命令の2進数表現の形式、フィールドで区切られる。
R形、I形、A形に分類される。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;アセンブリ言語&lt;/strong&gt;: 機械語を記号で置き換える言語である。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;算術論理演算命令&lt;/strong&gt;: 四則演算やシフトなど。レジスタとレジスタまたはレジスタと即値の間で演算がなされ、結果はレジスタに格納される。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;データ移動命令&lt;/strong&gt;: レジスタとメモリの間のデータのコピーを行う。
レジスタ間のデータ移動、&lt;strong&gt;メモリとレジスタの間のデータ移動&lt;/strong&gt;、メモリと入出力機器の間のデータ移動の3種類に大別される。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;分岐命令&lt;/strong&gt;: 制御の流れを変更する命令。無条件分岐命令と条件分岐命令に分類される。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;アドレッシング&lt;/strong&gt;: アドレッシング (addressing) とは、データや命令の居場所を特定することである。
メモリアドレスの生成方式。即値アドレッシング、ベース相対アドレッシング、レジスタアドレッシング、PC相対アドレッシングなどがある。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;サブルーチン&lt;/strong&gt;: 部分プログラムを再利用可能な形にしたもので、PCを含むレジスタの待避が必要である。スタックを用いる。&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;無条件分岐命令違いのまとめ&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;命令&lt;/th&gt;
&lt;th&gt;形式&lt;/th&gt;
&lt;th&gt;ターゲット指定方法&lt;/th&gt;
&lt;th&gt;特徴&lt;/th&gt;
&lt;th&gt;主な用途&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;j&lt;/td&gt;
&lt;td&gt;A&lt;/td&gt;
&lt;td&gt;直接指定 (絶対アドレス)&lt;/td&gt;
&lt;td&gt;単純な無条件分岐。サブルーチン呼び出しなし。&lt;/td&gt;
&lt;td&gt;任意のアドレスへのジャンプ&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;jr&lt;/td&gt;
&lt;td&gt;R&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;レジスタに格納されたアドレス&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;レジスタに保存されたアドレスへのジャンプ。&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;サブルーチンからの復帰&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;jal&lt;/td&gt;
&lt;td&gt;A&lt;/td&gt;
&lt;td&gt;直接指定 (絶対アドレス)&lt;/td&gt;
&lt;td&gt;サブルーチン呼び出し。戻りアドレスを&lt;code&gt;r31&lt;/code&gt;に保存。&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;サブルーチンの呼び出し&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr /&gt;
&lt;h2&gt;4. パイプライン処理&lt;/h2&gt;
&lt;h3&gt;第4章まとめ&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;パイプライン&lt;/strong&gt;: 流れ作業によって処理効率を飛躍的に向上させる技術&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;処理時間&lt;/strong&gt;: 一つの (命令の) 処理がはじまってから完了するまでの時間。
$\mathit{N}\times\mathit{T}$  ($\mathit{N}$はパイプラインのステージ数、$\mathit{T}$は1ステージの処理時間)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;スループット&lt;/strong&gt;: 単位時間に終了する処理量 (命令数) 。通常は$\frac{1}{T}$である。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;基本命令パイプライン&lt;/strong&gt;: 命令フェッチ($\mathrm{F}$)、命令デコード($\mathrm{D}$)、演算実行($\mathrm{E}$)、結果の格納($\mathrm{W}$)からなるコンピュータのパイプライン&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;パイプライン阻害要因&lt;/strong&gt;: 最も時間のかかるステージ、パイプラインレジスタなどによる遅延、ハザード&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;ハザード&lt;/strong&gt;: パイプライン動作ができなくなる状態。構造ハザード、データハザード、制御ハザードに分類される。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;構造ハザード&lt;/strong&gt;: コンピュータの内部構成に原因をもつハザード&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;データハザード&lt;/strong&gt;: 命令間のデータ依存関係に基づくハザード&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;制御ハザード&lt;/strong&gt;: 分岐命令によるハザード&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;フォワーディング&lt;/strong&gt;: 前の命令の実行結果を直接Eステージに送ることでデータハザードを解消する手法&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;制御ハザードの緩和法&lt;/strong&gt;: 命令アドレスの早期生成、遅延分岐、分岐予測、命令スケジューリング&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;分岐予測&lt;/strong&gt;: 分岐の有無を予測し、成功すれば続行、失敗すればパイプラインをフラッシュする。
2ビット予測器、2レベル適応形予測器など&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;hr /&gt;
&lt;h2&gt;5. キャッシュと仮想記憶&lt;/h2&gt;
&lt;h3&gt;第5章まとめ&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;記憶階層&lt;/strong&gt;: &lt;strong&gt;高速小容量&lt;/strong&gt;のメモリと&lt;strong&gt;低速大容量&lt;/strong&gt;のメモリを組み合わせて、見かけ上高速大容量のメモリを実現する技術&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;記憶階層の根拠&lt;/strong&gt;: メモリ参照の傾向は、&lt;strong&gt;時間的局所性&lt;/strong&gt;と&lt;strong&gt;空間的局所性&lt;/strong&gt;がある。
これを利用して、よく使うデータを上位階層のメモリに入れることで、効率的なメモリシステムが作られる。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;透明性&lt;/strong&gt;: 機械語プログラムの変更なく効率や安全性を高めるという性質。キャッシュや仮想記憶は透明性を持つ。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;キャッシュ&lt;/strong&gt;: 主記憶とCPUの間にある高速小容量一時メモリ。&lt;strong&gt;キャッシュライン&lt;/strong&gt;と呼ばれる単位で、主記憶との間でデータが交換される。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;ライトスルーとライトバック&lt;/strong&gt;: ライトスルー方式では、ストアのたびに主記憶の書込みが行われる。
ライトバック方式では、キャッシュラインの追出しが起こるときに主記憶の書込みが行われる。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;キャッシュの三の形&lt;/strong&gt;: ダイレクトマップ形、セットアソシアティブ形、フルアソシアティブ形。後にいくほど連想度が高く、ハードウェアが複雑になる。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;キャッシュミス&lt;/strong&gt;: &lt;strong&gt;初期参照ミス&lt;/strong&gt;、&lt;strong&gt;競合性ミス&lt;/strong&gt;、&lt;strong&gt;容量性ミス&lt;/strong&gt;の3種類 (三の&lt;strong&gt;C&lt;/strong&gt;) がある。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;仮想記憶&lt;/strong&gt;: 主記憶よりも大きなメモリ空間を実現し、複数のプログラムが一つの物理記憶を安全に分かちあって使うための技術&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;ページ&lt;/strong&gt;: 仮想記憶で、主記憶と二次記憶の間でデータをやりとりする単位。ふつう数キロバイト&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;ページテーブル&lt;/strong&gt;: 仮想アドレスから物理アドレスを導くためのテーブル。
主記憶に置かれる。ユーザプログラムが更新することはできない。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;ページフォールト&lt;/strong&gt;: ページが主記憶に入っていないときに起こる例外。主記憶の領域を確保し、二次記憶からページを読み込むための対処が必要となる。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;TLB&lt;/strong&gt;: ページテーブルのキャッシュ。メモリアクセスの高速化のために用いられる。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;仮想記憶とキャッシュ&lt;/strong&gt;: 直列形物理アドレスキャッシュ、並列形物理アドレスキャッシュ、仮想アドレスキャッシュなどの実現方式がある。&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;局所性&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;名前&lt;/th&gt;
&lt;th&gt;性質&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;空間的局所性&lt;/td&gt;
&lt;td&gt;あるメモリ語が参照されたときに、その語の近くの語が引き続き参照される性質&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;時間的局所性&lt;/td&gt;
&lt;td&gt;あるメモリ語が参照されたとき、その語が時間をおかずに再び参照される性質&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3&gt;キャッシュミス&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;名前&lt;/th&gt;
&lt;th&gt;英語&lt;/th&gt;
&lt;th&gt;定義&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;初期参照ミス&lt;/td&gt;
&lt;td&gt;compulsory miss, cold start miss&lt;/td&gt;
&lt;td&gt;キャッシュラインを最初にアクセスすることで起こるミス&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;競合性ミス&lt;/td&gt;
&lt;td&gt;conflict miss, collision miss&lt;/td&gt;
&lt;td&gt;同じインデックスをもつ異なるキャッシュラインにアクセスすることで起こるミス&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;容量性ミス&lt;/td&gt;
&lt;td&gt;capacity miss&lt;/td&gt;
&lt;td&gt;キャッシュしたいライン数がキャッシュ容量を上回ることで起こるミス&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;ちなみに、競合性ミスはフルアソシアティブ型キャッシュではおこらない。&lt;/p&gt;
&lt;h3&gt;ページフォールトによる中断&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;CPUの処理を一時中断する&lt;/li&gt;
&lt;li&gt;テーブルのエントリに入っている二次記憶上のアドレスから主記憶の空いている場所にページをコピーし、ページテーブルのエントリに物理ページアドレスを書き込み、有効ビットを1にする&lt;/li&gt;
&lt;li&gt;もし主記憶に空いた場所がなければ、どれか一つのページを二次記憶上に追い出し、空いた場所に必要とされるページを読み込むことになる&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;TLBの動作&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;メモリアクセスが起こると、仮想ページアドレスをタグとして、TLBが参照される。&lt;/li&gt;
&lt;li&gt;TLBがヒットすると、該当する物理ページアドレスが取り出され、ページ内オフセットと合わせて物理アドレスが作られる。&lt;/li&gt;
&lt;li&gt;TLBがミスすると、5.3.2項で述べたやり方で、ページテーブルが参照され、TLBが空いているエントリに、現在参照している仮想ページアドレスに対応する物理ページアドレスが入れられる。
TLBが空いていない場合は、LRUなどのやりかたでエントリが一つ空けられる。&lt;/li&gt;
&lt;/ol&gt;
&lt;hr /&gt;
&lt;h2&gt;6. 命令レベル並列処理とアウトオブオーダ処理&lt;/h2&gt;
&lt;h3&gt;第6章まとめ&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;並列処理&lt;/strong&gt;: 性能向上の手段。さまざまなレベルがある。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;命令レベル並列処理&lt;/strong&gt;: プロセッサの演算器を複数設けて命令を同時実行させる並列処理方式。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;VLIW&lt;/strong&gt;: 1命令の中に複数の演算を入れたアーキテクチャ。コンパイラが並列実行できる命令を決める。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;スーパスカラ&lt;/strong&gt;: 逐次実行の機械語プログラムから並列性を動的に抽出し、並列実行するアーキテクチャ。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;静的最適化&lt;/strong&gt;: 機械語プログラムが効率よく実行できるように、コンパイラなどが事前にあらかじめコードを調整しておく。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;ループアンローリング&lt;/strong&gt;: 小さなループを何個かまとめて一つのループとすることで、分岐命令によるハザードをなくす手法。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;ソフトウェアパイプライニング&lt;/strong&gt;: ループ間にまたがる命令を移動し、依存関係のある命令どうしの距離を離すことで、ハザードを起こりにくくし、並列度をあげる手法。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;アウトオブオーダ処理&lt;/strong&gt;: 命令を動的に入れ替えて実行効率をあげる動的方式。
ユーザに命令実行の順番を入れ替えるアウトオブオーダ実行と、実行結果をメモリに格納する順番を変えるアウトオブオーダ完了がある。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;データ依存&lt;/strong&gt;: フロー依存、逆依存、出力依存の3種類がある。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;フロー依存&lt;/strong&gt;: 命令Aで書き込んだ値を後続の命令Bで読み出すことで起こる依存関係。RAWハザードの原因となる。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;逆依存&lt;/strong&gt;: 命令Aで読み出したレジスタ (メモリ語) に後続の命令Bが書き込みを行うことで起こる依存関係。WARハザードの原因となる。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;出力依存&lt;/strong&gt;: 命令Aで書き込んだレジスタ (メモリ語) に後続の命令Bが再度書き込みを行うことで起こる依存関係。WAWハザードの原因となる。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;命令キュー&lt;/strong&gt;: アウトオブオーダ処理のための機構。
デコード後の複数の命令を格納し、実行可能な命令を取り出す機構が入っている。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;リザベーションステーション&lt;/strong&gt;: 機能ユニットごとに分散配置された命令キュー。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;レジスタリネーミング&lt;/strong&gt;: レジスタ番号の置き換えによって、逆依存、出力依存をなくし、並列性を向上させる手法。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;マッピングテーブル&lt;/strong&gt;: レジスタリネーミングを実現する機構の一つ。命令における論理レジスタアドレスを物理レジスタアドレスに変換する。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;リオーダバッファ&lt;/strong&gt;: 連想機構をもつメモリによってレジスタリネーミングを実現する機構。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;プロセッサの性能指標(例)&lt;/strong&gt;: クロック当りの平均実行命令数 $\times$ クロック周波数。&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;データ依存の分類&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;名前&lt;/th&gt;
&lt;th&gt;定義&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;フロー依存&lt;/td&gt;
&lt;td&gt;命令Aで書き込んだ値を後続の命令Bで読み出すことで起こる $\mathrm{A}\Rightarrow\mathrm{B}$ の依存関係。真の依存関係 (true dependence) ともいう。&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;逆依存&lt;/td&gt;
&lt;td&gt;命令Aで読み出したレジスタ (メモリ語) に後続の命令Bが書き込みを行うことで起こる $\mathrm{A}\Rightarrow\mathrm{B}$ の依存関係。&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;出力依存&lt;/td&gt;
&lt;td&gt;命令Aで書き込んだレジスタ (メモリ語) に後続の命令Bが再度書き込みを行うことで起こる $\mathrm{A}\Rightarrow\mathrm{B}$ の依存関係。&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;逆依存と出力依存は、主にレジスタ数の不足からくる依存である。&lt;/p&gt;
&lt;h4&gt;3種類の依存(例)&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;mul r1, r2, r3
add r4, r1, r5
add r5, r6, r7
add r4, r8, r9
add r10, r4, r11
add r12, r10, r13
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;フロー依存: $[1]\Rightarrow[2]$ (r1) 、$[2]\Rightarrow[5]$ (r4) 、$[4]\Rightarrow[5]$ (r4) 、$[5]\Rightarrow[6]$ (r10)&lt;/p&gt;
&lt;p&gt;逆依存: $[2]\Rightarrow[3]$ (r5)&lt;/p&gt;
&lt;p&gt;出力依存: $[2]\Rightarrow[4]$ (r4)&lt;/p&gt;
&lt;h3&gt;データ依存とデータハザード&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;データ依存&lt;/th&gt;
&lt;th&gt;データハザード&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;フロー依存&lt;/td&gt;
&lt;td&gt;RAW (read after write) ハザード&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;逆依存&lt;/td&gt;
&lt;td&gt;WAR (write after read) ハザード&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;出力依存&lt;/td&gt;
&lt;td&gt;WAW (write after write) ハザード&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
</content:encoded></item></channel></rss>