ITエンジニアなら知らないと恥ずかしい数学20選

「ITエンジニアなら知らないと恥ずかしい数学20選」――少し挑発的なタイトルにしました。
ではなぜ、あえてここまで強い言葉を使って、ITエンジニアに数学の必要性を訴えるのでしょうか。
処理の依存関係にはグラフがあり、アルゴリズムの速さには計算量があります。
確率・統計、行列、ベクトル、微分といった数学も、データ分析、機械学習、検索、画像処理など、さまざまなIT技術の中で使われています。
しかし数学を知ると、ただ動いているだけに見えていたコードの背後に、「なぜそうなるのか」という構造が見えてきます。
学んだ数学を、ITの現場で生かす。
そして、普段のコードやシステムの中にある数学を見つける。
その往復によって、数学もITも、より深く理解できるようになります。
ITの中に隠れている構造を見つけるための「数学のメガネ👓」を増やすことです。
数学そのものを確認するコードと、IT実務の中で同じ数学がどのように生きているのかを、Python、VBA、JavaScriptのコードとともに見ていきます。
| 掲載コードについて 本稿に掲載しているコードは、数学とコンピュータサイエンス(CS)の橋渡しとして、数学的な考え方がプログラムの中でどのように現れるのかを示すためのサンプルです。 数学的な構造そのものを確認する例に加えて、同じ構造がIT実務のコードにどのように現れるかを示す「IT実務での例」も掲載しています。 実用的なツールや業務処理として使用することを目的としたものではなく、特定の用途にそのまま利用できることを想定したものでもありません。 掲載コードは、すべての環境での動作を保証するものではありません。 実際に使用する場合は、コードの内容を十分に確認し、必要に応じて修正・検証したうえで、自己責任でご利用ください。 特にVBAなど、実際のデータを変更する可能性があるコードを実行する場合は、事前にバックアップを取るなど、十分に注意してください。 掲載コードの使用によって、データの破損、消失その他の損害が発生した場合でも、当方では責任を負いかねます。 |
※Pythonのコードには、標準ライブラリ以外のライブラリ(NumPy、SciPyなど)を使用しているものがあります。実行には別途インストールが必要な場合があります。
※本記事の数式および解説文章の多くは、生成AIを活用して作成しました。数式動作や解説文の最終的な内容は人間による確認・編集を経て掲載しています。
ページ内目次
02. 関係と順序(同値関係・半順序)
03. 集合と写像(定義域・終域・像)
04. グラフ理論(ノードとエッジ)
05. 木構造とハッシュ
06. 代数的構造と合同算術
第3章:不確実性とデータを見る数学(11~16)
12. 確率と条件付き確率
13. 確率分布(正規分布・二項分布など)
14. 統計的推定と仮説検定
15. 情報エントロピーと圧縮
16. ベイズの定理と確率的推論
おわりに:数学を知ると、ITの見え方が変わる
導入:なぜエンジニアに「数学のメガネ」が必要なのか
日々書いているコードの中には、すでに数学の構造が深く入り込んでいます。
条件分岐、繰り返し、状態管理、依存関係――こうしたプログラムの基本的な構造の背後には、離散数学や線形代数など、さまざまな数学的概念があります。
目の前の問題を別の角度から捉え、その背後にある構造や関係を見抜くための道具です。
「数学のメガネ」を持つことでコードの捉え方がどう変わるのか、2つの例で説明します。
例1:周期処理 ――「範囲外なら戻す」から「循環構造」へ
「0~11の盤面上を移動する」という処理を考えます。
def move(pos, step):
pos += step
while pos >= 12:
pos -= 12
while pos < 0:
pos += 12
return pos
Good:構造的思考(12を法とする合同算術)
def move(pos, step):
return (pos + step) % 12
「範囲から外れたら手動で戻す」という手続き思考から、「この問題の本質は12で一周する循環構造である」という数学的モデルへ捉え直したことです。
例2:処理順序 ――「FlagとIf文」から「依存関係のグラフ」へ
次に、タスク間に依存関係がある場合を考えます。
「Aが終わったらBとCを実行し、BとCの両方が終わったらDを実行する」という処理です。
if not a_done:
do_a()
a_done = True
if a_done and not b_done:
do_b()
b_done = True
if a_done and not c_done:
do_c()
c_done = True
if b_done and c_done and not d_done:
do_d()
d_done = True
タスクが増えたり依存関係が複雑になったりすると、フラグとIf文も増えていきます。
しかし、この問題を「実行手順」としてではなく、「タスク同士の依存関係」として見ると、別の姿が見えてきます。
Good:構造的思考(有向非巡回グラフ)
A ──→ B ──┐ │ │ └──→ C ──┴──→ D
この構造を、そのままデータとして表現してみます。
from graphlib import TopologicalSorter
tasks = {
"A": [],
"B": ["A"],
"C": ["A"],
"D": ["B", "C"]
}
order = list(TopologicalSorter(tasks).static_order())
print(order)
ここでは「Aを実行してからBを実行する」といった手順を直接コードに書いていません。
「BはAに依存する」「CはAに依存する」「DはBとCに依存する」という関係そのものを表現し、その構造から実行順序を導き出しています。
If文を増やして処理手順を管理するのではなく、問題そのものをグラフという数学的構造として表現し、その構造から必要な処理を導き出すのです。
本質:「どう処理するか」から「この問題は何なのか」へ
コードが短くなるのは結果に過ぎません。
本質は、問題を見るときのスタート地点が変わることにあります。
- 手続き思考(メガネなし):「どうやって順に処理を実行させるか?」(If文、フラグ、状態管理)
- 構造思考(数学のメガネ):「そもそも、この問題の背後にある構造は何なのか?」(周期、グラフ、集合)
そもそも、なぜIf文が必要になっているのか。
問題の捉え方を変えることで、必要だった処理そのものが不要になることがあります。
| 日常のプログラミング | 背後にある数学のメガネ(構造) |
|---|---|
| 条件分岐や仕様の整理 | 命題論理・述語論理 |
| 重複排除やグループ化 | 集合論・同値関係 |
| 周期処理や巡回バッファ | 合同算術 |
| 処理の依存関係やネットワーク経路 | グラフ理論 |
| 処理速度・スケーラビリティの評価 | 計算量(オーダー表記) |
| 不確実なデータやログからの推論 | 確率・統計・ベイズの定理 |
| 多次元データの変換 | 線形代数 |
ここから、ITエンジニアの世界観を変える20の数学概念を順に紐解いていきます。
第1章:構造を見る数学(01~06)
数値そのものではなく、条件の組み合わせ、対象同士の関係性、データの繋がりを捉えるための静的な数学のメガネを揃えます。
「何と何が同じなのか」「どの対象がどの対象と関係するのか」「どのような対応規則でデータが変換されるのか」といった、プログラムやデータの形そのものを記述するための数学です。
条件、集合、関係、写像、グラフ、代数的構造という見方を身につけると、別々のものに見えていたITの仕組みを、共通する構造として捉えられるようになります。
01. 命題論理と述語論理
たとえば P ∧ Q は P と Q がともに真のときだけ真になります。
さらに、対象 x とその対象が満たす条件 P(x) を考え、∀x P(x)(すべての x について P(x) が成り立つ)や ∃x P(x)(ある x について P(x) が成り立つ)によって、条件が成立する対象の範囲を表します。
命題論理が「命題同士の組み合わせ」を扱うのに対し、述語論理では「対象」と「その対象についての条件」まで含めて論理を表現します。
述語論理では、さらに「すべてのデータが条件を満たす」「少なくとも1件は条件を満たす」というように、対象の集まりと条件の関係まで表せます。
つまり、単なる Yes / No の判定から一歩進んで、「何について」「どの条件が」「どの範囲で」成り立つのかを構造として記述する数学です。
不要なネストや論理の破綻に気づきやすくなり、条件分岐をより整理された形で設計・見直すための判断材料になります。
ユーザー一覧について、「すべて有効か」「特定のユーザーが存在するか」を論理値として判定します。
users = [{"name": "Alice", "active": True}, {"name": "Bob", "active": True}]
all_active = all(u["active"] for u in users)
has_bob = any(u["name"] == "Bob" for u in users)
Sub CheckLogic()
Dim names As Variant, active As Variant
Dim i As Long
Dim allActive As Boolean, hasBob As Boolean
names = Array("Alice", "Bob")
active = Array(True, True)
allActive = True
hasBob = False
For i = LBound(names) To UBound(names)
If Not active(i) Then allActive = False
If names(i) = "Bob" Then hasBob = True
Next i
Debug.Print allActive, hasBob
End Sub
const users = [
{ name: "Alice", active: true },
{ name: "Bob", active: true }
];
const allActive = users.every(u => u.active);
const hasBob = users.some(u => u.name === "Bob");
console.log(allActive, hasBob);
ユーザーの状態・役割・停止フラグを組み合わせて、編集操作を許可するか判定します。
👓 数学のメガネ:業務上の権限判定も、P ∧ (Q ∨ R) ∧ ¬S のような命題の組み合わせとして整理できます。
user = {"active": True, "role": "editor", "suspended": False}
can_edit = (
user["active"]
and user["role"] in {"admin", "editor"}
and not user["suspended"]
)
print(can_edit)
Sub PermissionExample()
Dim active As Boolean, suspended As Boolean
Dim role As String
Dim canEdit As Boolean
active = True
role = "editor"
suspended = False
canEdit = active And _
(role = "admin" Or role = "editor") And _
Not suspended
Debug.Print canEdit
End Sub
const user = {
active: true,
role: "editor",
suspended: false
};
const canEdit =
user.active &&
["admin", "editor"].includes(user.role) &&
!user.suspended;
console.log(canEdit);
¬(P ∧ Q) = ¬P ∨ ¬Q、¬(P ∨ Q) = ¬P ∧ ¬Q です。
たとえば「有効かつ権限あり、ではない」という条件は、「無効、または権限なし」と同じ意味になります。
条件式の否定を展開したり、ガード節へ書き換えたりするときに、論理を崩さず整理するための実践的なルールです。
not (is_active and has_permission) # 同値 (not is_active) or (not has_permission)
Not (isActive And hasPermission) ' 同値 (Not isActive) Or (Not hasPermission)
!(isActive && hasPermission) // 同値 !isActive || !hasPermission
If 文の整理は、単なる書き方の工夫ではなく、論理式を同値な形へ変形する操作として捉えられます。02. 関係と順序(同値関係・半順序)
xRy を「x と y の間に関係 R がある」と表し、関係がどのような性質を持つかを公理で分類します。
反射律 xRx、対称律 xRy ⇒ yRx、推移律 xRy かつ yRz ⇒ xRz を満たす関係を同値関係といいます。
一方、反射律、反対称律 xRy かつ yRx ⇒ x=y、推移律を満たす関係を半順序といいます。
同値関係では「同じとみなせるもの」を分類でき、半順序では「どちらが先・上位・包含関係にあるか」といった順序を表現できます。
半順序は、「集合Aは集合Bに含まれる」のように、すべての対象を一直線に並べられなくても、対象同士の順序関係を表せます。
「同じものとしてまとめる」と「順序を付ける」という処理が、関係の性質として数学的に整理できます。
関係性や依存順序を整理することで、設計上の矛盾や問題のある依存関係を発見しやすくなります。
データをキーごとに分類し、同じキーを持つ要素をまとめます。
from itertools import groupby
data = [("A", 1), ("A", 2), ("B", 3)]
for key, group in groupby(data, lambda x: x[0]):
print(key, list(group))
※ itertools.groupby は、同じキーの要素が連続している範囲をグループ化します。キーの順序を問わず同じキーの要素をまとめたい場合は、あらかじめキーでソートしておく必要があります。
Sub GroupExample()
Dim dict As Object
Dim groups As Collection
Dim keys As Variant
Dim i As Long
Dim key As String
Dim value As Variant
Set dict = CreateObject("Scripting.Dictionary")
For i = 1 To 3
If i < 3 Then
key = "A"
Else
key = "B"
End If
value = i
If Not dict.Exists(key) Then
Set groups = New Collection
dict.Add key, groups
End If
Set groups = dict(key)
groups.Add value
Next i
keys = dict.keys
For i = LBound(keys) To UBound(keys)
Set groups = dict(keys(i))
Debug.Print keys(i) & ":"
For Each value In groups
Debug.Print " " & value
Next value
Next i
End Sub
const data = [["A", 1], ["A", 2], ["B", 3]];
const grouped = data.reduce((acc, [key, value]) => {
if (!acc[key]) {
acc[key] = [];
}
acc[key].push(value);
return acc;
}, {});
console.log(grouped);
viewer、editor、admin が持つ権限集合を比較し、ある役割の権限が別の役割に含まれているかを確認します。
👓 数学のメガネ:集合の包含 ⊆ は半順序の代表例です。「どちらが上位か」を単なる役職名ではなく、権限集合の関係として捉えられます。
viewer = {"read"}
editor = {"read", "write"}
admin = {"read", "write", "delete"}
print(viewer <= editor)
print(editor <= admin)
Function IsSubset(smaller As Object, larger As Object) As Boolean
Dim key As Variant
For Each key In smaller.Keys
If Not larger.Exists(key) Then Exit Function
Next key
IsSubset = True
End Function
Sub RoleOrderExample()
Dim viewer As Object, editor As Object, admin As Object
Set viewer = CreateObject("Scripting.Dictionary")
Set editor = CreateObject("Scripting.Dictionary")
Set admin = CreateObject("Scripting.Dictionary")
viewer("read") = True
editor("read") = True: editor("write") = True
admin("read") = True: admin("write") = True: admin("delete") = True
Debug.Print IsSubset(viewer, editor)
Debug.Print IsSubset(editor, admin)
End Sub
function isSubset(smaller, larger) {
return [...smaller].every(x => larger.has(x));
}
const viewer = new Set(["read"]);
const editor = new Set(["read", "write"]);
const admin = new Set(["read", "write", "delete"]);
console.log(isSubset(viewer, editor));
console.log(isSubset(editor, admin));
03. 集合と写像(定義域・終域・像)
X を定義域(domain)、Y を終域(codomain)といい、各 x ∈ X に対して f(x) ∈ Y を一つ対応させます。
実際に得られる値の集合 {f(x) | x ∈ X} を像(image)といいます。
写像は「入力された各要素に対して、出力を対応させる規則」です。
たとえば f(x)=x2 なら、1→1、2→4、3→9 という対応になります。
集合の要素を「選ぶ/選ばない」の0/1で表せば、各部分集合を0/1列として表現することもできます。
n個の要素には2n個の部分集合があるため、集合論がそのまま全探索の構造につながります。
写像は、その集合の要素を別の集合の要素へ一つずつ対応させる変換規則です。
そのため、写像は「入力データの一つ一つに同じルールを適用する」というプログラムの処理とよく似ています。
また、部分集合は各要素について0か1を持つ組として表せます。
3要素なら 23=8 通りとなり、「集合の数え上げ」が「探索する組合せの数」に直結します。
さらに、集合の要素を0/1で表せば、その部分集合をビットマスクとして列挙できることも見えてきます。
3要素なら23 = 8通りという「集合の大きさ」が、そのまま全探索の回数になります。
集合の各要素に同じ変換規則を適用し、入力集合から出力集合を得ます。
numbers = [1, 2, 3] squared = list(map(lambda x: x ** 2, numbers)) print(squared)
Sub MapExample()
Dim arr(2) As Long, i As Long
arr(0) = 1: arr(1) = 2: arr(2) = 3
For i = LBound(arr) To UBound(arr)
arr(i) = arr(i) * arr(i)
Next i
MsgBox arr(2)
End Sub
const numbers = [1, 2, 3]; const squared = numbers.map(x => x ** 2); console.log(squared);
集合の各要素を「選ぶ/選ばない」の0/1で表し、ビットマスクを変化させて部分集合をすべて列挙します。
items = ["A", "B", "C"]
for mask in range(1 << len(items)):
subset = [
items[i]
for i in range(len(items))
if mask & (1 << i)
]
print(subset)
Sub BitSearchExample()
Dim items As Variant
Dim mask As Long, i As Long
Dim subset As String
items = Array("A", "B", "C")
For mask = 0 To (2 ^ (UBound(items) + 1)) - 1
subset = ""
For i = 0 To UBound(items)
If (mask And CLng(2 ^ i)) <> 0 Then
subset = subset & items(i)
End If
Next i
Debug.Print subset
Next mask
End Sub
const items = ["A", "B", "C"];
for (let mask = 0; mask < (1 << items.length); mask++) {
const subset = [];
for (let i = 0; i < items.length; i++) {
if (mask & (1 << i)) {
subset.push(items[i]);
}
}
console.log(subset);
}
APIから受け取ったユーザーデータを、画面で使う displayName と status を持つデータへ変換します。
👓 数学のメガネ:入力集合の各要素に同じ規則を適用し、別の表現へ写す処理は「写像」として見ることができます。
users = [
{"first": "Taro", "last": "Yamada", "active": True},
{"first": "Hanako", "last": "Sato", "active": False},
]
view_models = [
{
"displayName": f'{u["last"]} {u["first"]}',
"status": "有効" if u["active"] else "停止",
}
for u in users
]
print(view_models)
Sub ApiMappingExample()
Dim firstName As Variant
Dim lastName As Variant
Dim active As Variant
Dim i As Long
firstName = Array("Taro", "Hanako")
lastName = Array("Yamada", "Sato")
active = Array(True, False)
For i = 0 To 1
Debug.Print lastName(i) & " " & firstName(i), _
IIf(active(i), "有効", "停止")
Next i
End Sub
const users = [
{ first: "Taro", last: "Yamada", active: true },
{ first: "Hanako", last: "Sato", active: false }
];
const viewModels = users.map(u => ({
displayName: `${u.last} ${u.first}`,
status: u.active ? "有効" : "停止"
}));
console.log(viewModels);
READ、WRITE、DELETE などの権限を1ビットずつ割り当て、複数の権限を1つの整数で表します。
AND は積集合、OR は和集合、XOR は一方だけに含まれる要素からなる対称差として見ることができます。
👓 数学のメガネ:ビットフラグの操作は、0と1の技巧ではなく、「どの権限を集合に含めるか」という集合演算として捉えられます。
READ = 0b0001 WRITE = 0b0010 DELETE = 0b0100 permission = READ | WRITE print(bool(permission & WRITE)) print(bool(permission & DELETE)) permission |= DELETE print(bool(permission & DELETE))
Sub PermissionBitMaskExample()
Const READ As Long = 1
Const WRITE As Long = 2
Const DELETE As Long = 4
Dim permission As Long
permission = READ Or WRITE
Debug.Print (permission And WRITE) <> 0
Debug.Print (permission And DELETE) <> 0
permission = permission Or DELETE
Debug.Print (permission And DELETE) <> 0
End Sub
const READ = 0b0001; const WRITE = 0b0010; const DELETE = 0b0100; let permission = READ | WRITE; console.log((permission & WRITE) !== 0); console.log((permission & DELETE) !== 0); permission |= DELETE; console.log((permission & DELETE) !== 0);
04. グラフ理論(ノードとエッジ)
辺は2つの頂点の間の接続関係を表し、頂点 u から v へ辺があれば、u と v が隣接していると考えます。
頂点と辺の向きを区別しないものを無向グラフ、向きを持たせたものを有向グラフといいます。
頂点をたどる経路(path)や、ある頂点から別の頂点へ到達できるかどうかなども、グラフ上の性質として表現できます。
つまり、グラフ理論は「対象」と「対象同士の接続」を数学的に扱う理論です。
道路網なら点が交差点、線が道路です。
システムなら点がサービス、線が呼び出し関係になります。
重要なのは、個々の対象そのものよりも「何と何がつながっているか」を扱えることです。
そのため、探索、経路、依存関係などを共通した構造として考えられます。
また、隣接するノードをたどるDFS(深さ優先探索)のように、グラフそのものが探索アルゴリズムの入力構造になります。
複雑なネットワーク構造を整理し、ボトルネックや分離すべき箇所を検討するための判断材料になります。
グラフの頂点を、ある頂点から隣接する頂点へ順にたどりながら深さ優先探索を行います。
graph = {
"A": ["B", "C"],
"B": ["D"],
"C": ["D"],
"D": []
}
def dfs(node, visited):
if node in visited:
return
visited.add(node)
print(node)
for next_node in graph[node]:
dfs(next_node, visited)
dfs("A", set())
Sub DFSExample()
Dim graph As Object
Dim visited As Object
Set graph = CreateObject("Scripting.Dictionary")
Set visited = CreateObject("Scripting.Dictionary")
graph.Add "A", Array("B", "C")
graph.Add "B", Array("D")
graph.Add "C", Array("D")
graph.Add "D", Array()
DFS "A", graph, visited
End Sub
Sub DFS(node As String, graph As Object, visited As Object)
Dim nextNode As Variant
If visited.Exists(node) Then Exit Sub
visited.Add node, True
Debug.Print node
For Each nextNode In graph(node)
DFS CStr(nextNode), graph, visited
Next nextNode
End Sub
const graph = {
A: ["B", "C"],
B: ["D"],
C: ["D"],
D: []
};
function dfs(node, visited) {
if (visited.has(node)) return;
visited.add(node);
console.log(node);
for (const nextNode of graph[node]) {
dfs(nextNode, visited);
}
}
dfs("A", new Set());
database が停止したとき、直接または間接的に影響を受けるサービスを依存グラフからたどります。
👓 数学のメガネ:サービスをノード、依存関係をエッジと見れば、障害影響調査はグラフ上の到達可能性の問題になります。
dependents = {
"database": ["order-api", "billing-api"],
"order-api": ["web"],
"billing-api": ["web"],
"web": [],
}
def affected(service, seen=None):
seen = seen or set()
for next_service in dependents[service]:
if next_service not in seen:
seen.add(next_service)
affected(next_service, seen)
return seen
print(affected("database"))
Sub ImpactExample()
Dim graph As Object, seen As Object
Set graph = CreateObject("Scripting.Dictionary")
Set seen = CreateObject("Scripting.Dictionary")
graph.Add "database", Array("order-api", "billing-api")
graph.Add "order-api", Array("web")
graph.Add "billing-api", Array("web")
graph.Add "web", Array()
TraceImpact "database", graph, seen
End Sub
Sub TraceImpact(service As String, graph As Object, seen As Object)
Dim nextService As Variant
For Each nextService In graph(service)
If Not seen.Exists(nextService) Then
seen.Add nextService, True
Debug.Print nextService
TraceImpact CStr(nextService), graph, seen
End If
Next nextService
End Sub
const dependents = {
"database": ["order-api", "billing-api"],
"order-api": ["web"],
"billing-api": ["web"],
"web": []
};
function affected(service, seen = new Set()) {
for (const nextService of dependents[service]) {
if (!seen.has(nextService)) {
seen.add(nextService);
affected(nextService, seen);
}
}
return seen;
}
console.log([...affected("database")]);
05. 木構造とハッシュ
有限の木 T=(V,E) では、頂点数を |V| とすると辺数は |E|=|V|-1 になります。
階層構造として見ると、根から各ノードへ至る経路が一意に定まるという性質を持ちます。
一方、ハッシュでは、キーの集合 K から配列の添字などの有限集合 {0,1,...,m-1} への写像 h : K → {0,1,...,m-1} を考えます。
異なるキーが同じ値に写る衝突(collision)が起こり得るため、その扱いも重要になります。
木とハッシュはどちらも、データを効率よく配置・探索するための構造として、集合や関係の数学からつながっています。
途中で輪にならないため、親子関係をたどる経路が整理されています。
ハッシュは、「キーを計算して、保存場所を決める」という発想です。
キーそのものを順番に探すのではなく、ハッシュ値を手掛かりに場所を絞り込みます。
同じ「探す」という処理でも、線形に並べる、階層化する、ハッシュ値で場所を決めるという構造の違いが、探索方法や計算量の違いになります。
用途に応じたデータ構造を選ぶための判断材料になります。
キーを使って値を検索し、キーと値の対応を利用した高速な参照を行います。
user_dict = {"usr_001": "Alice"}
print(user_dict.get("usr_001"))
Sub HashExample()
Dim dict As Object
Set dict = CreateObject("Scripting.Dictionary")
dict.Add "usr_001", "Alice"
MsgBox dict("usr_001")
End Sub
const userMap = new Map();
userMap.set("usr_001", "Alice");
console.log(userMap.get("usr_001"));
ノードが子ノードを参照する形で木構造を表現し、根から各ノードをたどります。ここでは深さ優先の前順走査(Preorder Traversal)を行います。
tree = {
"value": "A",
"left": {"value": "B", "left": None, "right": None},
"right": {"value": "C", "left": None, "right": None}
}
def preorder(node):
if node is None:
return
print(node["value"])
preorder(node["left"])
preorder(node["right"])
preorder(tree)
Sub TreeExample()
Dim value As Variant
Dim left As Variant
Dim right As Variant
value = Array("A", "B", "C")
left = Array(1, -1, -1)
right = Array(2, -1, -1)
Preorder value, left, right, 0
End Sub
Sub Preorder(value As Variant, left As Variant, right As Variant, index As Long)
If index < 0 Then Exit Sub
Debug.Print value(index)
Preorder value, left, right, left(index)
Preorder value, left, right, right(index)
End Sub
const tree = {
value: "A",
left: { value: "B", left: null, right: null },
right: { value: "C", left: null, right: null }
};
function preorder(node) {
if (node === null) return;
console.log(node.value);
preorder(node.left);
preorder(node.right);
}
preorder(tree);
部門の下にチームがぶら下がる組織階層を、根から再帰的にたどって表示します。
👓 数学のメガネ:組織図やメニュー、フォルダ構造は、実務では「木」として現れます。再帰処理はその階層構造を素直にたどる方法です。
organization = {
"name": "開発部",
"children": [
{"name": "基盤チーム", "children": []},
{"name": "業務チーム", "children": []},
],
}
def walk(node, depth=0):
print(" " * depth + node["name"])
for child in node["children"]:
walk(child, depth + 1)
walk(organization)
Sub OrganizationTreeExample()
Dim names As Variant
Dim parent As Variant
Dim i As Long
names = Array("開発部", "基盤チーム", "業務チーム")
parent = Array(-1, 0, 0)
PrintChildren names, parent, -1, 0
End Sub
Sub PrintChildren( _
names As Variant, _
parent As Variant, _
parentIndex As Long, _
depth As Long)
Dim i As Long
For i = LBound(names) To UBound(names)
If parent(i) = parentIndex Then
Debug.Print Space(depth * 2) & names(i)
PrintChildren names, parent, i, depth + 1
End If
Next i
End Sub
const organization = {
name: "開発部",
children: [
{ name: "基盤チーム", children: [] },
{ name: "業務チーム", children: [] }
]
};
function walk(node, depth = 0) {
console.log(" ".repeat(depth) + node.name);
node.children.forEach(child => walk(child, depth + 1));
}
walk(organization);
06. 代数的構造と合同算術
環や体は、加法と乗法など複数の演算について、さらに条件を加えて定義した代数的構造です。
つまり「どんな計算ができるか」だけでなく、「その計算がどのような法則に従うか」を数学的に扱います。
合同算術では、整数 a と b が正の整数 m に対して同じ余りを持つとき、a ≡ b (mod m) と表します。
これは a-b が m の倍数であること、すなわち m | (a-b) と同値です。
剰余演算では a mod m のように余りを取り出し、暗号などでは大きな整数のべき乗を法 m で計算する「べき乗剰余」も重要になります。
一方、合同算術は整数をそのまま並べるのではなく、「mで割った余りが同じもの」を同じ仲間として扱う見方です。
たとえば時計では、12時の次が13時ではなく1時になります。
これは12を法とする計算として考えることができ、一定の範囲を越えると最初に戻る循環構造を数学的に表せます。
この見方を持つと、単純な剰余演算が、ハッシュ、循環処理、チェックサム、暗号などに共通して現れる理由が見えてきます。
特に公開鍵暗号では、巨大な整数に対するべき乗を法の下で計算する「べき乗剰余」が重要な基本操作になります。
XOR(排他的論理和)のようなビット演算も、演算そのものが持つ性質を利用することで、可逆な処理やビット状態の操作に応用できます。
たとえばXORには、同じ値同士では0になる(A XOR A = 0)、0とのXORでは元の値が残る(A XOR 0 = A)という性質があり、これを利用すると一時変数を使わずに2つの整数値を交換することもできます。
ただし、XORによる値交換は性質を理解する例としては有名ですが、実務では可読性の高い通常の交換方法を使うのが一般的です。
さらに、mod を単なる余りを求める演算ではなく、値を一定の周期で折り返す仕組みとして見ると、ハッシュや暗号などに現れる処理を共通する数学的構造として捉えられるようになります。
整数を一定の法で割った余りを求め、周期的に値を扱う計算を確認します。
values = [10, 11, 12, 13, 24] modulus = 12 print([value % modulus for value in values])
Sub ModuloExample()
Dim values As Variant
Dim i As Long
Dim modulus As Long
values = Array(10, 11, 12, 13, 24)
modulus = 12
For i = LBound(values) To UBound(values)
Debug.Print values(i) Mod modulus
Next i
End Sub
const values = [10, 11, 12, 13, 24]; const modulus = 12; console.log(values.map(value => value % modulus));
ab をそのまま計算せず、法 m における余りを求めます。大きな整数を扱う暗号アルゴリズムで使われる基本的な処理です。
base = 7 exponent = 13 modulus = 10 print(pow(base, exponent, modulus))
Sub ModularPowerExample()
Dim base As Long, exponent As Long, modulus As Long
Dim result As Long
base = 7
exponent = 13
modulus = 10
result = 1
base = base Mod modulus
Do While exponent > 0
If exponent Mod 2 = 1 Then
result = (result * base) Mod modulus
End If
base = (base * base) Mod modulus
exponent = exponent \ 2
Loop
Debug.Print result
End Sub
※ 繰り返し二乗法による実装です。ただし、VBAの Long には扱える整数の範囲に制限があるため、暗号で扱うような巨大整数をそのまま計算する用途には使用できません。
let base = 7n;
let exponent = 13n;
const modulus = 10n;
let result = 1n;
while (exponent > 0n) {
if (exponent % 2n === 1n) {
result = (result * base) % modulus;
}
base = (base * base) % modulus;
exponent = exponent / 2n;
}
console.log(result.toString());
※ JavaScriptでは、整数リテラルの末尾に n を付けると BigInt になります。通常の Number では正確に扱える整数の範囲に限界があるため、大きな整数を扱う場合に使用します。ここでは、大きな整数を扱う場面を想定して BigInt を使用しています。BigInt と Number は混在させて演算できません。
担当者リストを順番に巡回し、日数が人数を超えても先頭へ戻る当番表を作ります。
👓 数学のメガネ:「最後まで行ったら先頭に戻す」という処理は、人数を法とする合同算術として書けます。
engineers = ["Aki", "Mika", "Ken"]
for day in range(7):
person = engineers[day % len(engineers)]
print(day + 1, person)
Sub RotationExample()
Dim engineers As Variant
Dim day As Long, index As Long
engineers = Array("Aki", "Mika", "Ken")
For day = 0 To 6
index = day Mod (UBound(engineers) + 1)
Debug.Print day + 1, engineers(index)
Next day
End Sub
const engineers = ["Aki", "Mika", "Ken"];
for (let day = 0; day < 7; day++) {
const person = engineers[day % engineers.length];
console.log(day + 1, person);
}
2のべき乗は、2進数で表すと1になっているビットが1つだけです。
たとえば 8 は
1000、7 は 0111 なので、8 AND 7 = 0 になります。この性質から、正の整数 n が2のべき乗かどうかを
n & (n - 1) で判定できます。👓 数学のメガネ:一見すると呪文のようなビット演算も、2進表現の構造を見ると「なぜ動くのか」を説明できます。
def is_power_of_two(n):
return n > 0 and (n & (n - 1)) == 0
print(is_power_of_two(8))
print(is_power_of_two(10))
Function IsPowerOfTwo(n As Long) As Boolean
If n <= 0 Then
IsPowerOfTwo = False
Else
IsPowerOfTwo = (n And (n - 1)) = 0
End If
End Function
Sub PowerOfTwoExample()
Debug.Print IsPowerOfTwo(8)
Debug.Print IsPowerOfTwo(10)
End Sub
function isPowerOfTwo(n) {
return n > 0n && (n & (n - 1n)) === 0n;
}
console.log(isPowerOfTwo(8n));
console.log(isPowerOfTwo(10n));
2つのビット列をXORすると、異なる位置のビットだけが1になります。
その1の個数(popcount)を数えれば、2つのビット列が何箇所異なるかを表す「ハミング距離」になります。
誤り検出、ビット列の比較、ハッシュや画像などの類似度判定につながる考え方です。
👓 数学のメガネ:XORは値交換の裏技だけではなく、「異なる部分を取り出す演算」として見ると、距離の計算へつながります。
a = 0b101101 b = 0b100001 distance = (a ^ b).bit_count() print(distance)
Function HammingDistance(a As Long, b As Long) As Long
Dim x As Long
x = a Xor b
Do While x <> 0
x = x And (x - 1)
HammingDistance = HammingDistance + 1
Loop
End Function
Sub HammingExample()
Debug.Print HammingDistance(45, 33)
End Sub
function bitCount(n) {
let count = 0;
while (n !== 0) {
n &= n - 1;
count++;
}
return count;
}
const a = 0b101101;
const b = 0b100001;
console.log(bitCount(a ^ b));※このJavaScript例は、説明を簡潔にするため非負の32ビット整数を対象としています。
第2章:繰り返しと近似を見る数学(07~10)
ここでは、その「繰り返し」「近似」「増え方」を数学的に捉えるためのメガネを揃えます。
極限や無限級数は、繰り返しを続けた先で何に近づくのかを考えます。
浮動小数点数の誤差では、数学上の実数を有限の情報で近似すると何が起こるのかを捉えます。
そして計算量では、入力の大きさが変わったときに処理量がどのように増えるのかを捉えます。
「処理を何回繰り返すのか」だけでなく、「繰り返すと何がどう変化するのか」「入力が増えると計算量がどう増えるのか」という視点を持つことが、この章のポイントです。
07. 数列と漸化式
漸化式は、前の項から次の項を決める関係式です。
たとえばフィボナッチ数列は a0=0、a1=1 を初期条件として、an=an-1+an-2 と表されます。
つまり漸化式では、n番目の値を単独で考えるのではなく、「現在の値が、それ以前の値からどのように生まれるか」という変化の規則を定義します。
また、差分作用素を Δan=an+1-an と定義すると、隣り合う値の変化量を取り出せます。
これは連続量を扱う微分に対する、離散的な変化の見方につながります。
差分は、その並びの中から「一つ進んだときにどれだけ変わったか」を取り出す操作です。
たとえば 10, 13, 18 なら、差分は 3, 5 になります。
こうして見ると、ループによる繰り返し計算は「前の状態から次の状態を生成する」という数列・漸化式の構造として捉えられます。
また、離散的なデータの変化を取り出す差分「Δan = an+1 - an」も、数列の考え方をそのままプログラムにしたものと見ることができます。
再帰や反復処理を状態遷移の規則として捉えることで、終了条件や状態の変化を整理しやすくなります。
漸化式に従って、第n項のフィボナッチ数を再帰的に計算します。
def fib(n):
if n < 2: return n
return fib(n - 1) + fib(n - 2)
Function Fib(n As Long) As Long
If n < 2 Then
Fib = n
Else
Fib = Fib(n - 1) + Fib(n - 2)
End If
End Function
function fib(n) {
if (n < 2) return n;
return fib(n - 1) + fib(n - 2);
}
console.log(fib(10));
離散データの隣り合う値の差を求め、数列の変化量を取り出します。
data = [10, 13, 18, 20, 25]
diff = [
data[i + 1] - data[i]
for i in range(len(data) - 1)
]
print(diff)
Sub DifferenceExample()
Dim data As Variant
Dim diff() As Long
Dim i As Long
data = Array(10, 13, 18, 20, 25)
ReDim diff(0 To UBound(data) - 1)
For i = 0 To UBound(diff)
diff(i) = data(i + 1) - data(i)
Next i
For i = 0 To UBound(diff)
Debug.Print diff(i)
Next i
End Sub
const data = [10, 13, 18, 20, 25];
const diff = [];
for (let i = 0; i < data.length - 1; i++) {
diff.push(data[i + 1] - data[i]);
}
console.log(diff);
現在庫に、その日の入庫・出庫による増減を反映しながら次の日の在庫を順番に求めます。
👓 数学のメガネ:「次の在庫 = 現在の在庫 + 当日の増減」という業務処理は、現在の状態から次の状態を決める漸化式そのものです。
stock = 100
changes = [-10, -20, 50, -15]
for change in changes:
stock = stock + change
print(stock)
Sub StockTransitionExample()
Dim stock As Long
Dim changes As Variant
Dim i As Long
stock = 100
changes = Array(-10, -20, 50, -15)
For i = LBound(changes) To UBound(changes)
stock = stock + changes(i)
Debug.Print stock
Next i
End Sub
let stock = 100;
const changes = [-10, -20, 50, -15];
for (const change of changes) {
stock = stock + change;
console.log(stock);
}
通信エラーなどで処理を再試行するとき、1秒、2秒、4秒、8秒……のように待機時間を増やします。
待機時間 an は、an+1 = 2an という漸化式で表せる等比数列です。
👓 数学のメガネ:
wait *= 2 という実務コードも、「前の値から次の値を決める」という漸化式そのものです。wait = 1
for retry in range(5):
print(retry + 1, wait)
wait *= 2
Sub ExponentialBackoffExample()
Dim wait As Long
Dim retry As Long
wait = 1
For retry = 1 To 5
Debug.Print retry, wait
wait = wait * 2
Next retry
End Sub
let wait = 1;
for (let retry = 1; retry <= 5; retry++) {
console.log(retry, wait);
wait *= 2;
}※実際の指数バックオフでは、待機時間の上限を設けたり、同時再試行の集中を避けるためにランダムな揺らぎ(jitter)を加えたりすることがあります。
08. 極限と無限級数
部分和 SN=∑n=0Nan を考え、N→∞ のとき SN がある値に近づくなら、その級数は収束するといいます。
たとえば等比級数 1 + 1/2 + 1/4 + ... は、部分和が 2 に近づき、∑n=0∞(1/2)n=2 となります。
コンピューターでは無限回の計算はできないため、有限個の項による部分和を計算して、無限の対象を有限の計算で近似します。
最初の何項まで計算したら、求めたい値にどれくらい近づくのかを考えます。
つまり、無限級数は「永遠に繰り返す計算」というより、「繰り返しを増やすと結果がどこへ近づくか」を考える数学です。
この視点を持つと、数値計算や近似計算で「何回計算すれば十分なのか」という問題も、極限や収束の問題として見えてきます。
また、無限級数を有限回だけ計算して近似する処理にも、極限という考え方が直接現れます。
「値が近づく」と「値が等しくなる」を区別できるようになり、数値計算における収束判定や近似計算を、単なるループではなく極限の考え方として捉えられるようになります。
各項を半分にしながら30項まで加算し、無限級数 1 + 1/2 + 1/4 + … の値に近づけます。
total = 0.0
term = 1.0
for _ in range(30):
total += term
term /= 2
print(total)
Sub InfiniteSeriesExample()
Dim total As Double
Dim term As Double
Dim i As Long
total = 0#
term = 1#
For i = 1 To 30
total = total + term
term = term / 2
Next i
Debug.Print total
End Sub
let total = 0;
let term = 1;
for (let i = 0; i < 30; i++) {
total += term;
term /= 2;
}
console.log(total);
レスポンスタイムの推定値を新しい観測値へ少しずつ近づけ、更新幅が十分小さくなったら反復を終了します。
👓 数学のメガネ:実務の反復計算でも「何回回すか」だけでなく「値がどこへ近づき、いつ十分に近づいたとみなすか」という収束の考え方が使われます。
estimate = 1000.0
target = 250.0
epsilon = 0.1
while True:
next_estimate = (estimate + target) / 2
if abs(next_estimate - estimate) < epsilon:
estimate = next_estimate
break
estimate = next_estimate
print(estimate)
Sub ConvergenceExample()
Dim estimate As Double, target As Double
Dim nextEstimate As Double, epsilon As Double
estimate = 1000#
target = 250#
epsilon = 0.1
Do
nextEstimate = (estimate + target) / 2
If Abs(nextEstimate - estimate) < epsilon Then
estimate = nextEstimate
Exit Do
End If
estimate = nextEstimate
Loop
Debug.Print estimate
End Sub
let estimate = 1000.0;
const target = 250.0;
const epsilon = 0.1;
while (true) {
const nextEstimate = (estimate + target) / 2;
if (Math.abs(nextEstimate - estimate) < epsilon) {
estimate = nextEstimate;
break;
}
estimate = nextEstimate;
}
console.log(estimate);
09. 浮動小数点数の誤差
しかしコンピューターは有限個のビットしか使えないため、すべての実数を正確に表現できるわけではありません。
浮動小数点数では、数値を有限精度で近似して表現するため、表現誤差や丸め誤差が生じます。
たとえば10進数の 0.1 は、2進数では有限桁で正確に表現できません。
そのため、プログラム内部では0.1を「0.1そのもの」ではなく、近い表現可能な値として保持することがあります。
この誤差が計算の途中で積み重なると、期待した値との小さな差として現れます。
表現できる数と数の間にある値を扱うときは、近い値へ丸める必要があります。
つまり「数学上では等しい」「コンピューター上では近い値として表現される」という違いがあります。
これはプログラミング言語の気まぐれではなく、有限の情報量で数を表現するというコンピューターの仕組みから生じる問題です。
金額のように正確な10進表現が重要な値では、整数化や十進型など、用途に応じた数値表現を選ぶ必要があります。
完全一致ではなく許容誤差で比較する、丸める場所を設計する、用途に応じて数値型を選択するといった判断を、数学的な理由を持って行えるようになります。
0.1 と 0.2 を加算し、浮動小数点数では10進数の値を完全には表現できない場合があることを確認します。
result = 0.1 + 0.2 print(result) print(result == 0.3)
Sub FloatingPointErrorExample()
Dim result As Double
result = 0.1 + 0.2
Debug.Print result
Debug.Print result = 0.3
End Sub
const result = 0.1 + 0.2; console.log(result); console.log(result === 0.3);
完全一致ではなく、二つの値の差が許容範囲内にあるかどうかで近似的な等価性を判定します。
import math a = 0.1 + 0.2 b = 0.3 epsilon = 1e-12 print(math.isclose(a, b, rel_tol=0.0, abs_tol=epsilon))
Sub ApproximateCompareExample()
Dim a As Double, b As Double
Dim epsilon As Double
a = 0.1 + 0.2
b = 0.3
epsilon = 0.000000000001
Debug.Print Abs(a - b) <= epsilon
End Sub
const a = 0.1 + 0.2; const b = 0.3; const epsilon = 1e-12; console.log(Math.abs(a - b) <= epsilon);
計算結果を単純な完全一致で比較せず、許容誤差以内なら同じ結果として扱うテストを行います。
👓 数学のメガネ:浮動小数点を使う処理では、「等しいか」ではなく「必要な精度の範囲で十分近いか」という比較が実務でも重要です。
actual = (0.1 + 0.2) * 100
expected = 30.0
epsilon = 1e-10
assert abs(actual - expected) <= epsilon
print("OK")
Sub NumericTestExample()
Dim actual As Double, expected As Double
Dim epsilon As Double
actual = (0.1 + 0.2) * 100
expected = 30#
epsilon = 0.0000000001
Debug.Assert Abs(actual - expected) <= epsilon
Debug.Print "OK"
End Sub
const actual = (0.1 + 0.2) * 100;
const expected = 30.0;
const epsilon = 1e-10;
console.assert(Math.abs(actual - expected) <= epsilon);
console.log("OK");
10. アルゴリズムの計算量(オーダー表記)
代表的なオーダー表記が O(1)、O(log n)、O(n)、O(n log n)、O(n2) などです。
たとえば、n個のデータを一度ずつ調べる処理は O(n)、n個のデータ同士をすべて組み合わせて調べる処理は O(n2) になります。
Big O は定数倍や低次の項を無視し、入力が大きくなったときの増え方に注目します。
計算量は「今のデータで何秒かかるか」ではなく、「データが増えたときに処理量がどのように増えるか」を数学的に表す尺度です。
O(n) と O(n2) は、少ないデータではそれほど違わなくても、nが大きくなると差が急激に広がります。
そのため、コードを見るときに「この処理は何回繰り返されるのか」だけでなく、「入力サイズに対して、その回数がどのように増えるのか」を考えることができます。
二重ループや探索方法の違いが性能へ与える影響を、実測だけでなく計算量の観点から判断する材料になります。
集合を使った検索と二重ループを比較し、データ件数が増えたときの処理量の違いを確認します。
list_a = [1, 2, 3] list_b = [2, 3, 4] # 二重探索:O(n × m) slow_result = [x for x in list_a if any(x == y for y in list_b)] # Setを使った探索:Set作成 O(m) + 探索 O(n)(平均的) set_b = set(list_b) fast_result = [x for x in list_a if x in set_b] print(slow_result) print(fast_result)
Sub OrderExample()
Dim listA As Variant, listB As Variant
Dim dict As Object
Dim slowResult As Collection, fastResult As Collection
Dim i As Long, j As Long
listA = Array(1, 2, 3)
listB = Array(2, 3, 4)
Set slowResult = New Collection
Set fastResult = New Collection
' 二重探索:O(n × m)
For i = LBound(listA) To UBound(listA)
For j = LBound(listB) To UBound(listB)
If listA(i) = listB(j) Then
slowResult.Add listA(i)
Exit For
End If
Next j
Next i
' Dictionaryを使った探索:作成 O(m) + 探索 O(n)(平均的)
Set dict = CreateObject("Scripting.Dictionary")
For j = LBound(listB) To UBound(listB)
dict(listB(j)) = True
Next j
For i = LBound(listA) To UBound(listA)
If dict.Exists(listA(i)) Then fastResult.Add listA(i)
Next i
For i = 1 To slowResult.Count
Debug.Print slowResult(i)
Next i
For i = 1 To fastResult.Count
Debug.Print fastResult(i)
Next i
End Sub
const listA = [1, 2, 3]; const listB = [2, 3, 4]; // 二重探索:O(n × m) const slowResult = listA.filter(x => listB.some(y => x === y)); // Setを使った探索:Set作成 O(m) + 探索 O(n)(平均的) const setB = new Set(listB); const fastResult = listA.filter(x => setB.has(x)); console.log(slowResult); console.log(fastResult);
売上明細の商品コードが商品マスタに存在するかをSet / Dictionaryで確認し、未知の商品コードだけを抽出します。
👓 数学のメガネ:大量データの突合は「各行ごとにマスタを最初から探す」のではなく、探索構造を先に作ることで計算量を大きく抑えられます。
master_codes = {"P001", "P002", "P003"}
sales_codes = ["P001", "P999", "P002", "P888"]
unknown = [code for code in sales_codes if code not in master_codes]
print(unknown)
Sub MasterLookupExample()
Dim master As Object
Dim sales As Variant
Dim code As Variant
Set master = CreateObject("Scripting.Dictionary")
master("P001") = True
master("P002") = True
master("P003") = True
sales = Array("P001", "P999", "P002", "P888")
For Each code In sales
If Not master.Exists(code) Then Debug.Print code
Next code
End Sub
const masterCodes = new Set(["P001", "P002", "P003"]); const salesCodes = ["P001", "P999", "P002", "P888"]; const unknown = salesCodes.filter(code => !masterCodes.has(code)); console.log(unknown);
ソート済みデータの中央を調べ、目的の値が左右どちらにあるかによって探索範囲を半分ずつ捨てていきます。
100万件のデータでも、220 = 1,048,576 なので、およそ20回「半分にする」だけで候補を1件程度まで絞れます。
この「何回2で割れば1付近まで小さくなるか」を表しているのが log2 n であり、二分探索の計算量 O(log n) の直感につながります。
👓 数学のメガネ:
O(log n) の log は単なる記号ではなく、「問題の大きさを何回半分にできるか」という回数として見ることができます。data = [10, 20, 30, 40, 50, 60, 70, 80, 90, 100]
target = 70
left, right = 0, len(data) - 1
while left <= right:
mid = (left + right) // 2
if data[mid] == target:
print(mid)
break
elif data[mid] < target:
left = mid + 1
else:
right = mid - 1
Sub BinarySearchExample()
Dim data As Variant
Dim target As Long
Dim left As Long, right As Long, mid As Long
data = Array(10, 20, 30, 40, 50, 60, 70, 80, 90, 100)
target = 70
left = LBound(data)
right = UBound(data)
Do While left <= right
mid = (left + right) \ 2
If data(mid) = target Then
Debug.Print mid
Exit Do
ElseIf data(mid) < target Then
left = mid + 1
Else
right = mid - 1
End If
Loop
End Sub
const data = [10, 20, 30, 40, 50, 60, 70, 80, 90, 100];
const target = 70;
let left = 0;
let right = data.length - 1;
while (left <= right) {
const mid = Math.floor((left + right) / 2);
if (data[mid] === target) {
console.log(mid);
break;
} else if (data[mid] < target) {
left = mid + 1;
} else {
right = mid - 1;
}
}
第3章:不確実性とデータを見る数学(11~16)
観測されたデータから背後の状態を推論し、確率的モデルとして捉えます。
結果が一つに決まらない現実のデータを、確率や統計によって数学的な対象として記述します。
この見方を持つと、ランダムなデータ、測定結果、検索結果、機械学習の予測などを、単なる数値の集まりではなく「確率的な構造を持つデータ」として捉えられるようになります。
11. 場合の数と順列・組み合わせ
異なる n 個から順序を考慮して r 個を選ぶ順列は nPr=n!/(n-r)!、順序を考慮せず r 個を選ぶ組合せは nCr=n!/(r!(n-r)!) で表されます。
順列では「A,B」と「B,A」を別のものとして数えますが、組合せでは同じものとして数えます。
この「順序を区別するかどうか」が、数え上げの基本的な分岐になります。
組合せの数は、全探索で調べなければならない候補数を事前に見積もるときにも使えます。
たとえば4人から2人を選ぶなら 4C2=6 通りです。
実際に6通りを列挙することもできますし、候補数だけを計算して探索が現実的かどうかを判断することもできます。
つまり、場合の数は単なる組合せ問題ではなく、「コンピューターが調べる候補の数を数学的に見積もる」ための考え方でもあります。
組み合わせの総数を先に求めれば、全探索が現実的な規模なのかを判断する材料にもなります。
全探索をすべきか、あるいはヒューリスティックや枝刈りを取り入れるべきかの適切な設計判断を下せます。
3個の要素から2個を順序を区別して選ぶすべての順列を列挙します。
import itertools perms = list(itertools.permutations([1, 2, 3], 2)) print(perms)
Sub PermutationExample()
Dim i As Long, j As Long
For i = 1 To 3
For j = 1 To 3
If i <> j Then
Debug.Print i & "," & j
End If
Next j
Next i
End Sub
const items = [1, 2, 3];
const permutations = [];
for (const a of items) {
for (const b of items) {
if (a !== b) permutations.push([a, b]);
}
}
console.log(permutations);
4個の要素から2個を順序を区別せずに選ぶすべての組み合わせを列挙します。
import itertools
items = ["A", "B", "C", "D"]
for combination in itertools.combinations(items, 2):
print(combination)
Sub CombinationExample()
Dim items As Variant
Dim i As Long, j As Long
items = Array("A", "B", "C", "D")
For i = LBound(items) To UBound(items)
For j = i + 1 To UBound(items)
Debug.Print items(i) & "," & items(j)
Next j
Next i
End Sub
const items = ["A", "B", "C", "D"];
for (let i = 0; i < items.length; i++) {
for (let j = i + 1; j < items.length; j++) {
console.log([items[i], items[j]]);
}
}
OS、ブラウザ、権限の候補から、確認すべきテスト条件を直積として列挙します。
👓 数学のメガネ:実務のテスト設計では、条件が増えるたびに候補数が掛け算で増えます。「組み合わせ爆発」を数える視点がテスト範囲の設計につながります。
import itertools
os_list = ["Windows", "macOS"]
browsers = ["Chrome", "Edge"]
roles = ["admin", "user"]
for case in itertools.product(os_list, browsers, roles):
print(case)
Sub TestMatrixExample()
Dim osList As Variant, browsers As Variant, roles As Variant
Dim i As Long, j As Long, k As Long
osList = Array("Windows", "macOS")
browsers = Array("Chrome", "Edge")
roles = Array("admin", "user")
For i = 0 To 1
For j = 0 To 1
For k = 0 To 1
Debug.Print osList(i), browsers(j), roles(k)
Next k
Next j
Next i
End Sub
const osList = ["Windows", "macOS"];
const browsers = ["Chrome", "Edge"];
const roles = ["admin", "user"];
for (const os of osList) {
for (const browser of browsers) {
for (const role of roles) {
console.log([os, browser, role]);
}
}
}
12. 確率と条件付き確率
事象 B が起こったという条件のもとで A が起こる確率を条件付き確率 P(A|B)=P(A∩B)/P(B)(P(B)>0)と定義します。
独立な事象では P(A∩B)=P(A)P(B) となります。
一方、条件付き確率では「Bが起きた」という新しい条件によって、Aの確率が変わることがあります。
この「条件が追加されると確率の見方が変わる」という構造は、データ分析や推論の基本になります。
条件付き確率では、すでに分かっている情報を前提にして、もう一度確率を考えます。
たとえば「全体の中でAがどれくらいあるか」と「Bであることが分かった人の中でAがどれくらいあるか」は、同じ確率とは限りません。
データが追加されることで、考える対象となる範囲が変わる。
この視点が、確率を単なる割合計算ではなく「条件付きの推論」として捉えるポイントです。
運用監視のアラート設計や障害分析でも、観測条件によって発生確率がどのように変化するかを整理し、事象同士の関連を定量的に検討するための材料になります。
1000件のデータのうち、条件を満たす集合の中でさらに別の条件を満たす確率を求めます。
total, spam, spam_and_keyword = 1000, 100, 80 prob_keyword_given_spam = spam_and_keyword / spam print(prob_keyword_given_spam)
Sub ProbabilityExample()
Dim total As Long
Dim spam As Long
Dim spamAndKeyword As Long
Dim probKeywordGivenSpam As Double
total = 1000
spam = 100
spamAndKeyword = 80
probKeywordGivenSpam = spamAndKeyword / spam
MsgBox probKeywordGivenSpam
End Sub
const total = 1000; const spam = 100; const spamAndKeyword = 80; const probKeywordGivenSpam = spamAndKeyword / spam; console.log(probKeywordGivenSpam);
監視ログ全体ではなく、「DB_TIMEOUTというアラートが出たケース」に条件を絞って、実際に障害へ至った割合を求めます。
👓 数学のメガネ:P(障害 | DB_TIMEOUT) を見ることで、全体の障害率とは別に「その条件が観測されたとき」のリスクを評価できます。
alert_count = 120 outage_with_alert = 36 p_outage_given_alert = outage_with_alert / alert_count print(p_outage_given_alert)
Sub ConditionalOutageExample()
Dim alertCount As Long, outageWithAlert As Long
Dim probability As Double
alertCount = 120
outageWithAlert = 36
probability = outageWithAlert / alertCount
Debug.Print probability
End Sub
const alertCount = 120; const outageWithAlert = 36; const pOutageGivenAlert = outageWithAlert / alertCount; console.log(pOutageGivenAlert);
13. 確率分布(正規分布・二項分布など)
連続型の正規分布では、平均 μ、標準偏差 σ によって確率密度関数 f(x)=1/(σ√(2π)) exp(-(x-μ)2/(2σ2)) を表せます。
二項分布では、成功確率 p の試行を n 回行ったとき、成功回数 X が k になる確率を P(X=k)=nCkpk(1-p)n-k と表します。
このように確率分布は、単一の値ではなく「値がどのようにばらついて現れるか」を数学的に表現します。
確率分布は、その「ばらつき方」に形を与える考え方です。
正規分布なら平均を中心に左右へ広がる形、二項分布なら成功・失敗を繰り返した結果としての成功回数の分布、と考えられます。
そのため、平均値だけを見るのではなく、「どの値がどれくらい現れやすいのか」というデータ全体の形を見ることができます。
データのばらつきや外れ値の頻度を確率モデルとして把握し、実態に即したスケーリングやリソース設計が可能になります。
正規分布に従う乱数を生成し、確率分布を持つ値をコンピューター上で扱います。
import random val = random.gauss(0, 1) print(val)
Function NormalRandom() As Double
Dim u1 As Double, u2 As Double
u1 = Rnd
Do While u1 = 0
u1 = Rnd
Loop
u2 = Rnd
NormalRandom = Sqr(-2# * Log(u1)) * Cos(2# * 3.14159265358979# * u2)
End Function
Sub NormalSampleExample()
Randomize
Debug.Print NormalRandom()
End Sub
function normalApprox() {
let u1 = Math.random();
while (u1 === 0) {
u1 = Math.random();
}
const u2 = Math.random();
return Math.sqrt(-2 * Math.log(u1)) * Math.cos(2 * Math.PI * u2);
}
console.log(normalApprox());
成功確率 p の試行を n 回行ったとき、成功回数が k 回になる確率を計算します。
import math n = 10 k = 6 p = 0.5 prob = math.comb(n, k) * p ** k * (1 - p) ** (n - k) print(prob)
Function Combination(n As Long, k As Long) As Double
Dim i As Long
Dim result As Double
result = 1
For i = 1 To k
result = result * (n - k + i) / i
Next i
Combination = result
End Function
Sub BinomialExample()
Dim n As Long, k As Long
Dim p As Double
Dim prob As Double
n = 10
k = 6
p = 0.5
prob = Combination(n, k) * p ^ k * (1 - p) ^ (n - k)
Debug.Print prob
End Sub
function combination(n, k) {
let result = 1;
for (let i = 1; i <= k; i++) {
result *= (n - k + i) / i;
}
return result;
}
const n = 10;
const k = 6;
const p = 0.5;
const prob = combination(n, k) * p ** k * (1 - p) ** (n - k);
console.log(prob);
平均値だけでなく、500msを超えたリクエストが全体の何割あるかを確認します。
👓 数学のメガネ:システム性能は平均値だけでは捉えきれません。分布の裾にある遅いリクエストの割合を見ることで、ばらつきを意識した評価になります。
latencies = [120, 150, 180, 220, 260, 310, 480, 520, 800, 1200] threshold = 500 slow_rate = sum(x > threshold for x in latencies) / len(latencies) print(slow_rate)
Sub LatencyDistributionExample()
Dim latencies As Variant
Dim threshold As Double
Dim slowCount As Long, i As Long
latencies = Array(120, 150, 180, 220, 260, 310, 480, 520, 800, 1200)
threshold = 500
For i = LBound(latencies) To UBound(latencies)
If latencies(i) > threshold Then slowCount = slowCount + 1
Next i
Debug.Print slowCount / (UBound(latencies) + 1)
End Sub
const latencies = [120, 150, 180, 220, 260, 310, 480, 520, 800, 1200]; const threshold = 500; const slowCount = latencies.filter(x => x > threshold).length; const slowRate = slowCount / latencies.length; console.log(slowRate);
14. 統計的推定と仮説検定
標本平均 x̄ は母平均 μ の推定量の一つです。
仮説検定では、まず帰無仮説 H0 を置き、その仮説のもとで観測した結果がどの程度起こりにくいかを検定統計量や p 値などで評価します。
たとえば2標本t検定では、2つの標本平均の差を標準誤差で規格化した t 統計量を用います。
重要なのは、標本で観測された差をそのまま「真の差」とみなすのではなく、標本抽出による偶然の変動を考慮することです。
統計では「この程度の差は、偶然のばらつきでも起こり得るのか」を考えます。
仮説検定は「差があることを証明する」というより、帰無仮説を前提にしたときに観測結果がどの程度珍しいかを評価する手続きです。
この考え方を知ると、数字の大小だけで結論を出すのではなく、標本のばらつきや不確実性まで含めてデータを見ることができます。
帰無仮説のもとで観測結果がどの程度起こりにくいのかを評価し、観測された差をどのように解釈するかの判断材料を得られるようになります。
2つの標本の平均差を、等分散を仮定しないWelchのt検定で評価します。PythonとVBAではp値を求め、JavaScriptでは簡潔さを優先してWelchのt統計量までを計算します。
import scipy.stats as stats a = [1, 2, 3] b = [2, 3, 4] t_stat, p_val = stats.ttest_ind(a, b, equal_var=False) print(p_val)
Sub TTestExample()
Dim a As Variant
Dim b As Variant
Dim pVal As Double
a = Array(1, 2, 3)
b = Array(2, 3, 4)
pVal = WorksheetFunction.TTest(a, b, 2, 3)
MsgBox pVal
End Sub
※ WorksheetFunction.TTest はExcelのワークシート関数をVBAから呼び出す方法です。実行にはExcel環境が必要です。
function mean(values) {
return values.reduce((sum, x) => sum + x, 0) / values.length;
}
function variance(values) {
const m = mean(values);
return values.reduce((sum, x) => sum + (x - m) ** 2, 0) / (values.length - 1);
}
const a = [1, 2, 3];
const b = [2, 3, 4];
const t = (mean(a) - mean(b)) /
Math.sqrt(variance(a) / a.length + variance(b) / b.length);
console.log(t);
旧APIと新APIで複数回計測したレスポンスタイムを、Welchのt検定で比較します。
👓 数学のメガネ:平均が少し違うだけで「改善した」と決めず、測定値のばらつきも含めて差を評価するのが仮説検定の実務的な使い方です。
from scipy import stats old_api = [420, 390, 450, 410, 430] new_api = [360, 340, 380, 355, 370] t_stat, p_value = stats.ttest_ind(old_api, new_api, equal_var=False) print(t_stat, p_value)
Sub ApiSpeedTTestExample()
Dim oldApi As Variant, newApi As Variant
Dim pVal As Double
oldApi = Array(420, 390, 450, 410, 430)
newApi = Array(360, 340, 380, 355, 370)
pVal = WorksheetFunction.TTest(oldApi, newApi, 2, 3)
Debug.Print pVal
End Sub
function mean(values) {
return values.reduce((sum, x) => sum + x, 0) / values.length;
}
function variance(values) {
const m = mean(values);
return values.reduce((sum, x) => sum + (x - m) ** 2, 0) / (values.length - 1);
}
const oldApi = [420, 390, 450, 410, 430];
const newApi = [360, 340, 380, 355, 370];
const t = (mean(oldApi) - mean(newApi)) /
Math.sqrt(variance(oldApi) / oldApi.length + variance(newApi) / newApi.length);
console.log(t);※Python版とVBA版ではp値を求めていますが、JavaScript版ではライブラリを使わず計算過程を示すため、Welchのt統計量までを求めています。
15. 情報エントロピーと圧縮
起こりにくい事象ほど大きな情報量を持ちます。
確率変数 X のシャノンエントロピーは H(X)=-∑ipilog2pi と定義され、1回の結果から得られる情報量の期待値を表します。
たとえば、必ず起こる事象の情報量は0ですが、確率1/4の事象なら -log2(1/4)=2 ビットです。
エントロピーは、結果の不確実性を平均的な情報量として数値化します。
自己情報量は、この「驚き」を数値として表す考え方です。
反対に、いつも同じ結果になるデータには新しい情報がほとんどありません。
結果が均等に分かれて予測しにくいほど、平均的な情報量であるエントロピーは大きくなります。
この見方から、データの偏りや冗長性、圧縮可能性を数学的に考えられるようになります。
通信量削減やストレージ効率化の強力な武器になります。
ある事象の確率 p から、その事象が持つ情報量 -log2(p) を計算します。
import math p = 0.25 information = -math.log2(p) print(information)
Sub SelfInformationExample()
Dim p As Double, info As Double
p = 0.25
info = -Log(p) / Log(2)
MsgBox info
End Sub
const p = 0.25; const information = -Math.log2(p); console.log(information);
ログ10000件のうち10件しか出ていない SECURITY_ALERT の出現確率から、そのイベントの自己情報量を計算します。
👓 数学のメガネ:頻繁なログより珍しいログほど自己情報量が大きくなります。異常検知やログ分析では「珍しさ」を定量的に見る発想につながります。
import math total_logs = 10000 security_alerts = 10 p = security_alerts / total_logs information = -math.log2(p) print(information)
Sub RareLogInformationExample()
Dim totalLogs As Long, securityAlerts As Long
Dim p As Double, information As Double
totalLogs = 10000
securityAlerts = 10
p = securityAlerts / totalLogs
information = -Log(p) / Log(2)
Debug.Print information
End Sub
const totalLogs = 10000; const securityAlerts = 10; const p = securityAlerts / totalLogs; const information = -Math.log2(p); console.log(information);
16. ベイズの定理と確率的推論
ここで P(θ) は事前確率、P(D|θ) は尤度、P(D) はデータが得られる確率です。
つまり、最初に持っていた仮説の確率に、観測したデータとの整合性を表す尤度を掛け合わせ、データ全体が得られる確率で正規化することで、観測後の確率を求めます。
ベイズ推論は「新しい情報を得たら、それまでの確率をどう更新するか」を数学的に表す仕組みです。
たとえば、ある仮説がもともとどの程度ありそうだったかと、その仮説なら今回のデータがどの程度起こりやすいかを組み合わせて、観測後の確率を計算します。
このため、ベイズの定理は「予測 → 観測 → 更新」という、データを受け取るたびに推論を更新する構造として捉えることができます。
事前確率と条件付き確率から、観測した結果のもとでの事後確率をベイズの定理で求めます。
p_a = 0.01 p_b_given_a = 0.9 p_b = 0.05 p_a_given_b = (p_b_given_a * p_a) / p_b print(p_a_given_b)
Sub BayesExample()
Dim pa As Double, pba As Double, pb As Double
pa = 0.01: pba = 0.9: pb = 0.05
MsgBox (pba * pa) / pb
End Sub
const pA = 0.01; const pBGivenA = 0.9; const pB = 0.05; const pAGivenB = (pBGivenA * pA) / pB; console.log(pAGivenB);
通常時の障害率、障害時にアラートが出る確率、正常時にも誤ってアラートが出る確率から、アラート観測後の障害確率を求めます。
👓 数学のメガネ:「アラートが出た = 障害」と即断せず、事前確率とアラートの信頼性を組み合わせて事後確率へ更新するのがベイズの考え方です。
p_outage = 0.01
p_alert_given_outage = 0.90
p_alert_given_normal = 0.05
p_alert = (
p_alert_given_outage * p_outage
+ p_alert_given_normal * (1 - p_outage)
)
p_outage_given_alert = p_alert_given_outage * p_outage / p_alert
print(p_outage_given_alert)
Sub AlertBayesExample()
Dim pOutage As Double
Dim pAlertGivenOutage As Double, pAlertGivenNormal As Double
Dim pAlert As Double, pOutageGivenAlert As Double
pOutage = 0.01
pAlertGivenOutage = 0.9
pAlertGivenNormal = 0.05
pAlert = pAlertGivenOutage * pOutage + _
pAlertGivenNormal * (1 - pOutage)
pOutageGivenAlert = pAlertGivenOutage * pOutage / pAlert
Debug.Print pOutageGivenAlert
End Sub
const pOutage = 0.01;
const pAlertGivenOutage = 0.90;
const pAlertGivenNormal = 0.05;
const pAlert =
pAlertGivenOutage * pOutage +
pAlertGivenNormal * (1 - pOutage);
const pOutageGivenAlert = pAlertGivenOutage * pOutage / pAlert;
console.log(pOutageGivenAlert);
第4章:変化と空間を見る数学(17~20)
ここでは、変化の大きさと方向、そして多次元の空間を扱うための数学のメガネを揃えます。
17. 微分・偏微分と勾配
多変数関数 f(x1,...,xn) では、各変数についての偏微分を並べた ∇f=( ∂f/∂x1,...,∂f/∂xn ) を勾配といいます。
勾配は関数が最も急に増加する方向を示すベクトルです。
そのため、損失を小さくしたい場合は、勾配とは反対方向へ進むという考え方が勾配降下法につながります。
微分は「変化率」、勾配は「多次元での変化率の向きと大きさ」を扱う数学です。
勾配は、多次元の関数に対してこれを数学的に表すものです。
損失を小さくしたいなら、最も増加する方向である勾配の反対へ少しずつ進みます。
更新式 x←x-η∇f(x) が、その考え方を表しています。
したがって機械学習のパラメータ調整は、単なる試行錯誤ではなく、「関数の傾きを計算して、より小さい方向へ移動する」という数学的な操作として見えてきます。
f(x)=x2 の微分 f'(x)=2x を使い、現在位置から損失が小さくなる方向へxを繰り返し更新します。
x = 10.0
lr = 0.1
for _ in range(5):
grad = 2 * x
x -= lr * grad
print(x)
Sub OptimizationExample()
Dim x As Double, lr As Double, i As Long
x = 10#: lr = 0.1
For i = 1 To 5
x = x - lr * (2 * x)
Next i
MsgBox x
End Sub
let x = 10.0;
const lr = 0.1;
for (let i = 0; i < 5; i++) {
const grad = 2 * x;
x -= lr * grad;
}
console.log(x);
簡略化した性能コスト関数を使い、バッチサイズを勾配の反対方向へ少しずつ動かします。
👓 数学のメガネ:実際のチューニングでは評価関数はもっと複雑ですが、「パラメータをどちらへ動かせば評価値が下がるか」を勾配で決める考え方は同じです。
batch_size = 20.0
learning_rate = 0.1
for _ in range(10):
# cost = (batch_size - 64)^2 + 100
gradient = 2 * (batch_size - 64)
batch_size -= learning_rate * gradient
print(batch_size)
Sub BatchSizeTuningExample()
Dim batchSize As Double, learningRate As Double
Dim gradient As Double, i As Long
batchSize = 20#
learningRate = 0.1
For i = 1 To 10
gradient = 2 * (batchSize - 64)
batchSize = batchSize - learningRate * gradient
Next i
Debug.Print batchSize
End Sub
let batchSize = 20.0;
const learningRate = 0.1;
for (let i = 0; i < 10; i++) {
const gradient = 2 * (batchSize - 64);
batchSize -= learningRate * gradient;
}
console.log(batchSize);
18. 行列と線形変換
線形変換 T は T(x+y)=T(x)+T(y)、T(cx)=cT(x) を満たします。
2次元の回転なら、角度 θ に対して R=[[cosθ,-sinθ],[sinθ,cosθ]] と表せます。
行列を掛け合わせれば、複数の線形変換を一つの変換として合成できます。
平行移動は線形変換ではないため、回転・拡大縮小と同じ行列形式で扱うには同次座標を使うなどの拡張が必要になります。
たとえば画像上の点を回転させる場合、点ごとに別々の公式を書くのではなく、回転行列を掛けるだけで同じ変換を適用できます。
そのため、行列を知ると、画像処理や3Dグラフィックス、機械学習で大量に行われる計算を「ベクトルを別の空間へ写す変換」として統一的に見ることができます。
回転や拡大縮小を行列として扱い、同次座標を使えば平行移動も含めて、複数の空間変換を一つの行列として合成できます。
行列とベクトルを掛け合わせ、行列による線形変換を数値計算として実行します。
import numpy as np matrix = np.array([[1, 2], [3, 4]]) vector = np.array([1, 1]) print(np.dot(matrix, vector))
Sub MatrixVectorMultiplyExample()
Dim matrix(1 To 2, 1 To 2) As Double
Dim vector(1 To 2) As Double
Dim result(1 To 2) As Double
matrix(1, 1) = 1#: matrix(1, 2) = 2#
matrix(2, 1) = 3#: matrix(2, 2) = 4#
vector(1) = 1#: vector(2) = 1#
result(1) = matrix(1, 1) * vector(1) + matrix(1, 2) * vector(2)
result(2) = matrix(2, 1) * vector(1) + matrix(2, 2) * vector(2)
Debug.Print result(1)
Debug.Print result(2)
End Sub
const matrix = [[1, 2], [3, 4]];
const vector = [1, 1];
const result = matrix.map(row =>
row.reduce((sum, value, i) => sum + value * vector[i], 0)
);
console.log(result);
1600×1200の画像を50%に縮小するとき、画像上の点 (800, 600) を行列で新しい座標へ変換します。
👓 数学のメガネ:画像処理では、各点を個別の式で扱うのではなく、座標ベクトルに同じ変換行列を適用することで処理を統一できます。
import numpy as np scale = np.array([[0.5, 0.0], [0.0, 0.5]]) point = np.array([800, 600]) new_point = scale @ point print(new_point)
Sub ImageScaleExample()
Dim x As Double, y As Double
Dim scale As Double
Dim newX As Double, newY As Double
x = 800#: y = 600#
scale = 0.5
newX = scale * x + 0 * y
newY = 0 * x + scale * y
Debug.Print newX, newY
End Sub
const scale = [[0.5, 0.0], [0.0, 0.5]];
const point = [800, 600];
const newPoint = scale.map(row =>
row.reduce((sum, value, i) => sum + value * point[i], 0)
);
console.log(newPoint);
19. 固有値・固有ベクトル
つまり、行列による変換を行っても、固有ベクトルは同一直線上に保たれ、λ に応じて伸縮します。λ が負の場合は向きが反転します。
固有値は det(A-λI)=0 を満たす値として求めることができ、対応する固有ベクトルは (A-λI)v=0 を解くことで求められます。
その中に「変換後も同一直線上に保たれる特別な方向」が存在することがあります。
その特別な方向を表すのが固有ベクトルで、どれだけ伸縮するかを表す数が固有値です。固有値が負なら向きは反転します。
行列を単なる大量の掛け算としてではなく、「空間の中の方向をどう変化させるか」という観点から見るための数学になります。
ここで重要なのは個別の応用技術を覚えることではなく、「変換の中に保たれる特別な方向がある」という固有値・固有ベクトルの考え方です。
特に、繰り返し変換したときにどの方向が残り、どの方向が強く伸びるのかという問題を、固有値・固有ベクトルという共通の数学として捉えられるようになります。
2×2行列と固有ベクトルを使い、A v と λ v が一致することを数値的に確認します。
import numpy as np A = np.array([[2, 1], [1, 2]], dtype=float) v = np.array([1, 1], dtype=float) lam = 3.0 print(A @ v) print(lam * v) print(np.allclose(A @ v, lam * v))
Sub EigenvectorCheckExample()
Dim a11 As Double, a12 As Double
Dim a21 As Double, a22 As Double
Dim vx As Double, vy As Double, lambda As Double
Dim av1 As Double, av2 As Double
a11 = 2#: a12 = 1#
a21 = 1#: a22 = 2#
vx = 1#: vy = 1#: lambda = 3#
av1 = a11 * vx + a12 * vy
av2 = a21 * vx + a22 * vy
Debug.Print av1, av2
Debug.Print lambda * vx, lambda * vy
Debug.Print Abs(av1 - lambda * vx) < 0.000000000001 And _
Abs(av2 - lambda * vy) < 0.000000000001
End Sub
const A = [[2, 1], [1, 2]];
const v = [1, 1];
const lambda = 3;
const Av = [
A[0][0] * v[0] + A[0][1] * v[1],
A[1][0] * v[0] + A[1][1] * v[1]
];
const lambdaV = v.map(x => lambda * x);
console.log(Av);
console.log(lambdaV);
console.log(Av.every((x, i) => Math.abs(x - lambdaV[i]) < 1e-12));
一定の条件を満たす場合、ベクトルに行列を繰り返し掛けて正規化することで、絶対値最大の固有値に対応する固有ベクトルへ近づけます。
import numpy as np
A = np.array([[2, 1], [1, 2]], dtype=float)
v = np.array([1.0, 0.2])
for _ in range(20):
v = A @ v
v = v / np.linalg.norm(v)
print(v)
Sub PowerIterationExample()
Dim a11 As Double, a12 As Double
Dim a21 As Double, a22 As Double
Dim x As Double, y As Double, nx As Double, ny As Double
Dim norm As Double, i As Long
a11 = 2#: a12 = 1#
a21 = 1#: a22 = 2#
x = 1#: y = 0.2
For i = 1 To 20
nx = a11 * x + a12 * y
ny = a21 * x + a22 * y
norm = Sqr(nx * nx + ny * ny)
x = nx / norm
y = ny / norm
Next i
Debug.Print x, y
End Sub
const A = [[2, 1], [1, 2]];
let v = [1, 0.2];
for (let i = 0; i < 20; i++) {
const x = A[0][0] * v[0] + A[0][1] * v[1];
const y = A[1][0] * v[0] + A[1][1] * v[1];
const norm = Math.hypot(x, y);
v = [x / norm, y / norm];
}
console.log(v);
3ページのリンク遷移を行列で表し、スコアベクトルへ繰り返し掛けることで、リンク構造の中で相対的に重要なページを求めます。
👓 数学のメガネ:ランキング計算では、行列を繰り返し適用したときに残る方向が重要になります。固有ベクトルは「ネットワーク全体で安定するスコア」の考え方につながります。
import numpy as np
transition = np.array([
[0.0, 0.5, 1.0],
[0.5, 0.0, 0.0],
[0.5, 0.5, 0.0],
])
rank = np.array([1/3, 1/3, 1/3])
for _ in range(20):
rank = transition @ rank
print(rank)
Sub PageRankLikeExample()
Dim m(1 To 3, 1 To 3) As Double
Dim rank(1 To 3) As Double, nextRank(1 To 3) As Double
Dim i As Long, j As Long, k As Long
m(1, 1) = 0: m(1, 2) = 0.5: m(1, 3) = 1
m(2, 1) = 0.5: m(2, 2) = 0: m(2, 3) = 0
m(3, 1) = 0.5: m(3, 2) = 0.5: m(3, 3) = 0
rank(1) = 1 / 3: rank(2) = 1 / 3: rank(3) = 1 / 3
For k = 1 To 20
For i = 1 To 3
nextRank(i) = 0
For j = 1 To 3
nextRank(i) = nextRank(i) + m(i, j) * rank(j)
Next j
Next i
For i = 1 To 3
rank(i) = nextRank(i)
Next i
Next k
Debug.Print rank(1), rank(2), rank(3)
End Sub
const transition = [
[0.0, 0.5, 1.0],
[0.5, 0.0, 0.0],
[0.5, 0.5, 0.0]
];
let rank = [1 / 3, 1 / 3, 1 / 3];
for (let k = 0; k < 20; k++) {
rank = transition.map(row =>
row.reduce((sum, value, i) => sum + value * rank[i], 0)
);
}
console.log(rank);
20. ベクトル空間と内積
2つのベクトル a=(a1,...,an)、b=(b1,...,bn) の内積は a·b=∑i=1naibi で定義されます。
内積は a·b=|a||b|cosθ とも表せるため、二つのベクトルの向きの関係を測ることができます。
特に、ベクトルを正規化すると、内積によってコサイン類似度を表せます。
ベクトル空間は、多次元のデータを「点」や「方向」として扱うための数学的な枠組みです。
内積の値はベクトルの長さにも影響されますが、長さを1に正規化したベクトル同士なら、同じ方向ほど内積は1に近く、直角なら0、反対方向なら-1になります。
このように正規化すれば、内積によって「向きがどれくらい似ているか」を比較できます。
このため、文章や画像などをベクトルに変換すると、単純な文字一致ではなく「ベクトルの方向がどれくらい近いか」という幾何学的な比較が可能になります。
2つのベクトルの各成分を掛けて足し合わせ、内積を計算します。
import numpy as np v1 = np.array([1, 2, 3]) v2 = np.array([2, 4, 6]) dot_prod = np.dot(v1, v2) print(dot_prod)
Sub DotProductExample()
Dim v1(0 To 2) As Double, v2(0 To 2) As Double
Dim dotProd As Double
v1(0) = 1: v1(1) = 2: v1(2) = 3
v2(0) = 2: v2(1) = 4: v2(2) = 6
dotProd = v1(0) * v2(0) + v1(1) * v2(1) + v1(2) * v2(2)
MsgBox dotProd
End Sub
const v1 = [1, 2, 3]; const v2 = [2, 4, 6]; const dotProd = v1.reduce((sum, value, i) => sum + value * v2[i], 0); console.log(dotProd);
検索文を表すベクトルと複数の文書ベクトルのコサイン類似度を計算し、最も方向が近い文書を探します。
👓 数学のメガネ:RAGやベクトル検索では、文章そのものを比較する代わりに、埋め込みベクトルの方向の近さを内積・コサイン類似度で評価します。
import math
query = [0.8, 0.1, 0.4]
documents = {
"manual": [0.9, 0.1, 0.3],
"invoice": [0.1, 0.9, 0.2],
"faq": [0.7, 0.2, 0.5],
}
def cosine(a, b):
dot = sum(x * y for x, y in zip(a, b))
norm_a = math.sqrt(sum(x * x for x in a))
norm_b = math.sqrt(sum(x * x for x in b))
return dot / (norm_a * norm_b)
for name, vector in documents.items():
print(name, cosine(query, vector))
Function CosineSimilarity(a As Variant, b As Variant) As Double
Dim i As Long
Dim dot As Double, normA As Double, normB As Double
For i = LBound(a) To UBound(a)
dot = dot + a(i) * b(i)
normA = normA + a(i) * a(i)
normB = normB + b(i) * b(i)
Next i
CosineSimilarity = dot / (Sqr(normA) * Sqr(normB))
End Function
Sub VectorSearchExample()
Dim query As Variant
query = Array(0.8, 0.1, 0.4)
Debug.Print "manual", CosineSimilarity(query, Array(0.9, 0.1, 0.3))
Debug.Print "invoice", CosineSimilarity(query, Array(0.1, 0.9, 0.2))
Debug.Print "faq", CosineSimilarity(query, Array(0.7, 0.2, 0.5))
End Sub
function cosine(a, b) {
const dot = a.reduce((sum, x, i) => sum + x * b[i], 0);
const normA = Math.sqrt(a.reduce((sum, x) => sum + x * x, 0));
const normB = Math.sqrt(b.reduce((sum, x) => sum + x * x, 0));
return dot / (normA * normB);
}
const query = [0.8, 0.1, 0.4];
const documents = {
manual: [0.9, 0.1, 0.3],
invoice: [0.1, 0.9, 0.2],
faq: [0.7, 0.2, 0.5]
};
for (const [name, vector] of Object.entries(documents)) {
console.log(name, cosine(query, vector));
}
おわりに:数学を知ると、ITの見え方が変わる
実際、数学を意識することなく、多くのプログラムを書くことができます。
それでも、数学を知ることで見えてくるものがあります。
集合や写像を知れば、データの抽出や変換を数学的な構造として見ることができます。
グラフを知れば、ネットワークや依存関係を「点と線の構造」として捉えられます。
確率や統計を知れば、不確実なデータを扱うときに、その背後にある考え方が見えてきます。
線形代数を知れば、多次元のデータや変換を「空間」として見ることができます。
一見すると普通の業務処理でも、数学のメガネをかけると、その背後に漸化式、集合、グラフ、確率、行列といった共通の構造が見えてきます。
しかし、知っていることで、同じコードやシステムが少し違って見えてきます。
数学を学ぶことは、計算できることを増やすだけではありません。
ITの中にある構造を見つけるための「見方」を増やすことでもあります。
むしろ、本稿で取り上げた20の概念は、数学全体から見れば入り口の、そのさらに手前にあるものにすぎません。
離散数学、線形代数、微積分、確率・統計をさらに深く学んでいけば、その先には数値解析、最適化、情報理論、代数学など、ITやコンピュータサイエンスにつながる多くの数学があります。
プログラムを書いていて「なぜこうなるのだろう」と疑問を持ったとき、アルゴリズムを見て「この背後にはどんな構造があるのだろう」と考えたとき、その都度、必要な数学へ戻って学べばよいのです。
その往復を繰り返すたびに、以前は単なる処理にしか見えなかったものの中に、新しい構造が見えてくるはずです。
その先には、もっと多くの見方があります。
同じテーマ「エクセル雑感」の記事
電卓とプログラムと私
「緩衝材」としてのVBAとRPA|その終焉とAIの台頭
開いている「Excel/Word/PowerPoint」ファイルのパスを調べる方法
Excelのシート5分類
Excel主要関数 習得難易度 10段階(文字列・日付除く)
【ITとは何か】Excel・VBAを学ぶ前に知っておきたい「4つの本質」
生成AIが暴いた「VBAオワコン説」の盲点|問われるのは言語ではなく評価軸
ITエンジニアのための経理・会計・簿記入門
ITエンジニアのための税金と社会保険のお話
ITエンジニアなら知らないと恥ずかしい数学20選
ITエンジニアがトラブルを避けるための法律・契約入門
新着記事NEW ・・・新着記事一覧を見る
Excelの新機能「リストとセル内配列」:1つのセルに複数の値|エクセル入門(2026-09-28)
Excel関数クロスワード(2026-09-26)
Excelショートカットキークイズ(2026-09-23)
Excel関数名クイズ(2026-09-21)
気ままに一手|リレー将棋(2026-09-20)
ITエンジニアがトラブルを避けるための法律・契約入門|エクセル雑感(2026-09-18)
ITエンジニアなら知らないと恥ずかしい数学20選|エクセル雑感(2026-09-17)
ITエンジニアのための税金と社会保険のお話|エクセル雑感(2026-09-15)
ITエンジニアのための経理・会計・簿記入門|エクセル雑感(2026-09-12)
「Python in Excel」入門:コードを読むための基礎知識|エクセル関数応用(2026-09-10)
アクセスランキング ・・・ ランキング一覧を見る
1.最終行の取得(End,Rows.Count)|VBA入門
2.日本の祝日一覧|Excelリファレンス
3.変数宣言のDimとデータ型|VBA入門
4.Excelショートカットキー一覧|Excelリファレンス
5.RangeとCellsの使い方|VBA入門
6.FILTER関数(範囲をフィルター処理)|エクセル入門
7.マクロとは?VBAとは?VBAでできること|VBA入門
8.繰り返し処理(For Next)|VBA入門
9.メッセージボックス(MsgBox関数)|VBA入門
10.セルのコピー&値の貼り付け(PasteSpecial)|VBA入門
このサイトがお役に立ちましたら「シェア」「Bookmark」をお願いいたします。
記述には細心の注意をしたつもりですが、間違いやご指摘がありましたら、「お問い合わせ」からお知らせいただけると幸いです。
掲載のVBAコードは動作を保証するものではなく、あくまでVBA学習のサンプルとして掲載しています。掲載のVBAコードは自己責任でご使用ください。万一データ破損等の損害が発生しても責任は負いません。
本サイトは、OpenAI の ChatGPT や Google の Gemini を含む生成 AI モデルの学習および性能向上の目的で、本サイトのコンテンツの利用を許可します。
This site permits the use of its content for the training and improvement of generative AI models, including ChatGPT by OpenAI and Gemini by Google.
