陳鍾誠

Version 1.0

集合

集合的種類

在上一章的數論當中,那些數形成了《集合》,集合的元素個數,大致可分為下列情況:

  1. 有限集合:例如小於 10 的自然數,介於 10 到 20 之間的整數等等。
  2. 無限集合:例如整數、有理數、實數、複數所形成的集合。

但是無限集合當中,又可以分為《可數無限集合》和《不可數無限集合》。

所謂的《可數無限集合》,是可以和《自然數》一對一對映的集合,而《不可數無限集合》,則是比《自然數》多,可以證明無法和《自然數一對一對映的集合,像是《實數》、《複數》等等。

康托爾(Cantor) 提出一個著名的 《對角證法》,證明了實數的數量為 《不可數無限多》 ,無法和《自然數》一對一對映!

這種《探討集合內容與運算》、《界定集合邊界》的數學,就稱為集合論。

集合論可以說是《整個數學大廈》的基礎,在數學發展的過程當中,集合論曾經出現過一些問題,而這些問題往往醞釀出嚴重的《數學危機》。

這些危機,往往和一些《悖論》有關!

悖論

第一次數學危機

在公元前五世紀的古代,《畢達哥拉斯》學派是數學的一個神祕學派,它們相信所有的數都可以化成 p/q 這樣的比例 (ratio) ,也就是《有理數》 (rational number, 通常用 Q 代表)。

但是畢達哥拉斯學派弟子希伯斯發現。他以幾何方法證明 $\sqrt{2}$ 無法用《比例》表示,也就是無法寫成 p/q。換句話說 $\sqrt{2}$ 不是有理數,或者說是無理數。

後來希伯斯觸犯學派章程,將無理數透露給外人,因而被處死,其罪名竟然等同於「瀆神」。這就是所謂的 第一次數學危機

第一次的數學危機,導致了《無理數》的發現,因此才會建立《實數》的概念,甚至後來還建立了《複數》。

第二次數學危機

第二次數學危機,是由《無窮小》這個概念所引發的,因為《無窮小》所造成的弔詭現象,古希臘的《芝諾》提出了幾個悖論:

芝諾的第一個悖論稱為《飛矢不動論》,描述如下:

芝諾問他的學生 「一支射出的箭是動的還是不動的?」
「那還用說,當然是動的。」
「確實是這樣,在每個人的眼裡它都是動的。
  可是,這支箭在每一個瞬間裡都有它的位置嗎?」
「有的,老師。」
「在這一瞬間裡,它佔據的空間和它的體積一樣嗎?」
「有確定的位置,又佔據著和自身體積一樣大小的空間。」
「那麼,在這一瞬間裡,這支箭是動的,還是不動的?」
「不動的,老師」
「這一瞬間是不動的,那麼其他瞬間呢?」
「也是不動的,老師」
「所以,射出去的箭是不動的?」

芝諾的第二個悖論是《阿基里斯永遠追不上烏龜》,那阿基里斯就是希臘神話中最勇猛的英雄,參與了特洛伊戰爭,被稱為「希臘第一勇士」。

芝諾說,這個跑得超快的勇士,永遠追不上一隻爬得超慢的烏龜!

芝諾悖論之「阿基里思與烏龜」 論述如下:

動得最慢的物體不會被動得最快的物體追上。

由於追趕者首先應該達到被追者出發之點,此時被追者
已經往前走了一段距離。

因此被追者總是在追趕者前面。 

來源:—亞里士多德, 物理學 VI:9, 239b15

Zeno's paradoxes:Achilles and the tortoise

– In a race, the quickest runner can never 
overtake the slowest, since the pursuer 
must first reach the point whence the 
pursued started, so that the slower must 
always hold a lead.

在HPM(數學史與數學教學,History and Pedagogy of Mathematics)這份研究中,列出了相關數學家對無窮小的看法:

人物年代對無窮小量的觀點,或處理方法
芝諾(希臘語:Ζήνων)約前490年-前430年提出飛矢不動論、阿基里斯跑不過烏龜等悖論
歐幾里得公元前300年窮竭法:他們相信用間接法才能使面積問題獲得嚴格證明。
卡瓦列里(B. Cavalieri)1598-1647把無窮小量的辦法推進了一步(見祖暅原理)
沃利斯(J. Wallis)1616-1703他對極限的定義「含有正確的想法,但用詞不嚴謹」
萊布尼茲1646-1716其演算法很成功,但「對概念不太確定」。他對於「消失中的量」的立場是複雜的,而且隨時間而變。
歐拉1707-1783獲得了很多重要結果,但不考慮真正無窮小量帶來的困難。其觀點受十七世紀典型的科學思維框架影響。
達朗貝爾(J. d’Alembert)1717-1783拒絕承認「消失中的量」。他給出過極限的定義,但措辭不明確。
拉格朗日1736-1813也拒絕承認無窮小量,企圖把微積分歸結為代數。
柯西1789-1857其寫下的定義至今依然通用,由當時可以使用的數學語言寫成。

這個數學危機,一直從古希臘時代就存在了,直到 19 世紀初柯西(法語:Augustin Louis Cauchy) 寫了一系列的分析學論文,將微積分嚴格公理化之後,才算解決了此一問題。

19世紀微積分學的準則並不嚴格,柯西拒絕當時微積分學的說法,並定義了一系列的微積分學準則。他一生共發表800多篇論文。其中較為有名的是《分析教程》、《無窮小分析教程概論》和《微積分在幾何上的應用》。

柯西建構起我們大學時期微積分所要學的那種無窮小 $\epsilon$ 理論,讓微積分成為一門嚴格而沒有矛盾的數學,解決了第二次的數學危機。

這門建構《微積分基礎》的數學,就稱為《分析學》!

第三次數學危機

第三次數學危機的主角,正是集合論,關鍵人物是《康托爾》和《羅素》。

1870年代由《康托爾及理察·戴德金》提出的《樸素集合論》,後來被更加精確地構建為 《公理化集合論》 ,以解決《樸素集合論》中的《悖論》問題。

最著名的《樸素集合論》悖論,是羅素所提出的,描述如下

由《所有不包含集合自身的集合所構成的集合》用數學符號表示為:

    A={x|x ∉ x}
    
問題是: A 自己到底算不算 A 的成員呢?    

仔細分析、您可以看到其中的矛盾!

羅素和懷海德在撰寫《數學原理》這部巨著快完成的時候,發現了上述的悖論,還有理髮師悖論,結果羅素在書的後記裏說:

科學家遇到最不幸的事情,莫過於當一件偉大的工作完成的時候,它的基礎卻早已倒塌

對於一般集合,不管是有限或無限,可數或不可數,我們通常可以寫出這樣的判斷函數,但是對於那些特別詭異的問題,像是《羅素的集合悖論》、《理髮師悖論》等等之類的集合,我們沒有辦法寫函數來求解,因為這種集合會造成數學矛盾,電腦也無法百分之百正確判斷!

為了解決集合論的悖論問題,數學家們可以說是想盡了各種辦法。

從《羅素悖論》發現開始,《數學家們》逐漸發展出《公理化集合論》,但是這樣的集合論雖然可以透過無法構造出《羅素悖論集合》而避開問題,但由於是採用《一階邏輯出發的》,所以不太適合用在電腦上。

更糟糕的是,即使採用了《公理化集合論》,仍然會遭遇一些《悖論》問題,這些《悖論》比較不嚴重,像是 《布拉利-福爾蒂悖論》 ,還有 《康托爾悖論》

要瞭解這些悖論的成因,首先要理解何謂 《序數》《基數》,然後看看《康托爾》等人是如何定義序數和基數的。

直覺上、基數代表集合的大小,像是集合 {1,2,3} 的基數就是 3,而序數代表順序,像是《第一、第二、第三》等等。

==== 以下段落來自 維基百科:基數 =====

康托爾在1874年-1884年引入最原始的集合論(現稱樸素集合論)時,首次引入基數概念。

他最先考慮的是集合{1,2,3}和{2,3,4},它們並非相同,但有相同的基數。驟眼看來,這是顯而易見,但究竟何謂兩個集合有相同數目的元素?

康托爾的答案,是透過所謂的一一對應,即把兩個集合的元素一對一的排起來——若能做到,兩個集合的基數自然相同。這答案,容易理解但卻是革命性的,因為用相同的方法即可比較任意集合的大小,包括無窮集合。

最先被考慮的無窮集合是自然數集 N = {1, 2, 3, …}及其無限子集。他把所有與 N 能一一對應的集為可數集。令康托爾意外的是,原來N的所有無限子集都能與N一一對應。他把N的基數稱為 N0,是最小的 阿列夫數 (往上還有 N1, N2, …)。

康托爾發現,原來有理數集合與代數數集合也是可數的。於是乎在1874年初,他嘗試證明是否所有無限集合均是可數,其後他得出了實數集不可數的結論。原先的證明用到了涉及區間套的複雜論證,而在他1891年的論文中,他以簡單而巧妙的對角論證法重新證明了這一結果。實數集的基數,記作 C,代表連續統。

接著康托爾構作一個比一個大的集合,得出一個比一個大的基數,而這些巨大集合的元素已不可如實數般書寫出來。因此關於基數的一般理論,需要一個新的語言描述,這就是康托爾發明集合論的主因。

康托爾隨後提出 《連續統假設》:該假設認為 C 就是第二個超窮基數 N1,即繼 N0 之後最小的基數。

對於《連續統假設》成立或不成立,可以分為兩種情況:

(a). 成立: N0 < N1 = Card(R) (b). 不成立: N0 < N1 < Card(R)

康托爾當時採用的集合論稱為《樸素集合論》,這是在《羅素悖論被提出之前的事情了》!

後來為了處理悖論,數學家們發展出了《公理化集合論》,像是 ZFC 這樣的公理系統。

ZFC 發展出來之後,數學家們開始試圖去證明《連續統假設》,結果卻發現證明不了。

後來《哥德爾》和《柯恩》分別證明了連續統假設和集合論其餘公理是相容且獨立的。

1940年《哥德爾》證明了上述的 (a) 式與集合論其餘公理相容。

1963年《柯恩》證明了上述 (b) 式與集合論其他公理也相容。

所以現在我們知道《連續統假設》是不能證明的,即接受或否定它會得出兩套以上不同但邏輯上可行的公理化集合論。

這個結果和《歐氏幾何》的平行公設非常相似,承認與否認《連續統假設》都可以建構出完整且一致的集合論。

無限集的基數

最小的無限集合是自然數集。{1, 2, 3, 4,…, n, …} 與 {2, 4, 6, 8, …, 2n, …} 基數相同,因為可以讓前一集合的 n 與後一集合的 2n 一一對應。從這個例子可以看出,對於一個無窮集合來說,它可以和它的一個真子集有相同的基數。

無限集合具有下列特性:(下列四種定義等價)

  1. 它不與任何自然數等勢 (等勢代表具有同樣基數,可以一對一映射)。
  2. 它有超過一個等勢的序數。(兩種以上可以一對一映射的序數)
  3. 它有至少一個真子集和它等勢。
  4. 存在由自然數集到它的單射。

對角證法

《康托爾》透過一對一映射定義基數,發現《自然數、整數、有理數、代數數》之間都可以一對一映射。

於是《康托爾》開始企圖證明《所有無限集合均是可數的這件事情》。

結果大感意外的是,他反而證明出了《實數集是不可數的》這個定理。

該定理原先的證明相對複雜,但是後來在 1891 年的論文中,他以簡單而巧妙的 《對角論證法》 重新證明了《實數集是不可數的》。

證明了《實數集不可數》之後,《康托爾》實數集的基數,記作 C,代表連續統。

得出了實數集不可數的結論。

實數集合的大小

用下列方程式可以將 (0,1) 之間的實數集合,一對一映射到所有實數上。

$y=\frac{2x-1}{x^2}$

連續統假設

對每一個基數,存在一個最小比它大的基數。這在自然數當然是對的。自然數集的基數是 N0,康托爾稱下一個為 N1,相類似的,還定義了如下序列:N0, N1, N2, ….。

(注意 $C = 2^{N0}$ )

連續統假設猜想,就是 C=N1 。

連續統假設是與一般集論公理(即Zermelo-Fraenkel公理系統加上選擇公理)是獨立的。

更一般的廣義連續統假設假設是 $N_{n+1}=2^{N_n}$ ,也就是對所有無窮基數 N,都不存在介乎 N 與 $2^N$ 之間的基數。

必須特別注意的是,這個假設沒有被證明,康托爾花了一輩子,最後還陷入赤貧,並且罹患躁鬱症,但始終沒有辦法證明連續統假說。

集合論的程式實作方法

集合之間可以進行《聯集、交集、差集》等運算,這種《集合與運算》形成了 《集合代數》

有限集合

在電腦中,要表達集合的方式有很多,像是用《陣列》或《字典》都可以代表集合。

在 javascript 當中,用《字典或陣列》代表《有限集合》是最方便的做法。

舉例而言,假如果們想用一個代表《血型》,用陣列就可以宣告如下:

var bloodType = ["A", "B", "AB", "O"];

JavaScript 中的字典,傳統做法是用物件 (Object)。血型

var bloodDictionary = { 
  A: "隨興", 
  B: "正直", 
  AB: "固執", 
  O: "樂觀"
}

然而、由於用物件充當字典會有一些副作用,像是「成員函數會被當作集合內容,只能用字串代表元素」等等問題,於是在 ECMAScript 6 (ES6) 版的標準當中,決定加入 Set, Map 等型態,以便能避開這些問題。

以下是這些新型態《集合與映射》的用法:

檔案: es6setmap.js

var c = console;

var bloodSet = new Set(["A", "B", "AB", "O"]);

var A = ["A","隨興"], B=["B","正直"], AB=["AB", "固執"], O=["O", "樂觀"];

var bloodMap = new Map([A,B,AB,O]);

c.log('bloodSet.has(A)=', bloodSet.has("A"));
c.log('bloodSet.has(C)=', bloodSet.has("C"));
c.log('bloodMap.has(A)=', bloodMap.has("A"));
c.log('bloodMap.has(C)=', bloodMap.has("C"));
c.log('bloodMap.get(A)=', bloodMap.get("A"));
c.log('bloodMap.get(C)=', bloodMap.get("C"));

執行結果:

D:\Dropbox\gitbook\rlab\code\set>node es6setmap.js
bloodSet.has(A)= true
bloodSet.has(C)= false
bloodMap.has(A)= true
bloodMap.has(C)= false
bloodMap.get(A)= 隨興
bloodMap.get(C)= undefined

除此之外、ES6 還加入了 WeakSet, WeakMap 等型態,其鍵值(key) 只能是物件,不能是基本型態,這種設計方式有助於垃圾蒐集機制快速回收記憶體 (不會留下一大堆無法回收的物件)。

無限集合

上述範例是對於有限集合而言,但是對於無限集合 (包含可數無限和不可數無限集合) ,我們應該怎麼處理呢?

對於電腦而言,通常無法代表《無限》的事物,因此我們用有上下限的《整數》來代表數學上的《整數》,用《浮點數》來代表數學上的《實數》。

如果我們要判斷一個數字是否在某集合內,可以撰寫函數來達成這件事,只是我們判斷的對象,通常是《電腦裡能表達的那種數字型態》,在 javascript 裏通常就是 Number 。

舉例而言,我們可以寫出下列來判斷一個數是否為《自然數》:

檔案:naturalNumber.js

var c = console;

function natural(n) {
	return n>=0 && n%1===0;
}

c.log("natural(3)=", natural(3));
c.log("natural(3.8)=", natural(3.8));
c.log("natural(-5)=", natural(-5));

執行結果:

D:\Dropbox\gitbook\rlab\code\set>node biggerThan.js
natural(3)= true
natural(3.8)= false
natural(-3)= false

甚至對於像質數這種數學上較難的問題,我們也可以寫個程式來判斷:

檔案: isPrime.js

var c = console;

function isPrime(n) {
  for (var i=2; i<n; i++)
    if (n % i === 0)
      return false;
  return true;
}

c.log("isPrime(27)=", isPrime(27));
c.log("isPrime(13)=", isPrime(13));

執行結果:

D:\Dropbox\gitbook\rlab\code\set>node isPrime
isPrime(27)= false
isPrime(13)= true

同樣的,對於《浮點數》我們也可以寫類似的判斷函數,以判斷某數是否在指定集合中。

程式解析

Rlab 的 set.js 模組當中定義了各種常見集合與操作,以下是原始程式碼。

檔案: lib/set.js

var S = {}
var I = require("./integer");

S.PI = Math.PI;
S.E  = Math.E;

extend = S.extend = Object.assign;
// ================= Rule =====================
var check = S.check = S.assert = function(cond, msg) {
	if (cond)
		console.log("O:"+msg);
	else {
		console.log("X:"+msg);
		if (S.throwError) throw Error('check fail!');
	}
}

be = S.be =function(msg,cond) { return check(cond, msg) }

S.proto=function(o) { return Object.getPrototypeOf(o) }

// relation
var eq=S.eq=function(a,b)  {
  return (typeof a === typeof b) && a.toString()===b.toString() 
}
S.neq=function(a,b)  { return !S.eq(a,b) }
S.leq=function(a,b)  { return a<=b }
S.geq=function(a,b)  { return a>=b }
S.lt =function(a,b)  { return a<b  }
S.gt =function(a,b)  { return a>b  }

// ========= type checking =================
S.yes=function(a) { return true }
S.no=function(a) {return false }
S.isBool=function(a) { 
  return typeof a === 'boolean' || a instanceof Boolean 
}
S.isFunction=function(a) { 
  return typeof a==='function' || a instanceof Function 
}
S.isString=function(a) { 
  return typeof a==='string' || a instanceof String 
}
S.isObject=function(a) { 
  return typeof a==='object' || a instanceof Object 
}
S.isArray=function(a) { 
  return a instanceof Array 
}
S.isUndefined=function(a) { 
  return typeof a === 'undefined' 
}
S.isSet=function(a) { 
  return a instanceof Set 
}
S.isFloat=S.isNumber=function(a) { 
  return typeof a === 'number' || a instanceof Number 
}
S.isInteger=function(a) { return S.isFloat(a) && a%1===0 }
S.isZero=function(a) { return a===0 }
S.isPositive=function(a) { return a>0 }
S.isNegative=function(a) { return a<0 }
S.isEven=function(a) { return (S.isInteger(a) && a%2===0) }
S.isOdd=function(a) { return (S.isInteger(a) && a%2===1) }

// ========== Random ==============
S.random=function(a,b) {
  return a+Math.random()*(b-a);
}

S.randomInt=function(a,b) {
  return Math.floor(S.random(a,b));
}

S.sample=function(a) {
  return a[S.randomInt(0,a.length)]; 
}

// ========= Set ===================
Set.prototype.union = function(setB) {
    var union = new Set(this);
    for (var elem of setB) {
        union.add(elem);
    }
    return union;
}

Set.prototype.intersection = function(setB) {
    var intersection = new Set();
    for (var elem of setB) {
        if (this.has(elem)) {
            intersection.add(elem);
        }
    }
    return intersection;
}

Set.prototype.difference = function(setB) {
    var difference = new Set(this);
    for (var elem of setB) {
        difference.delete(elem);
    }
    return difference;
}

Set.prototype.enumerate = function(n) {
	var array=[], values = this.values();
	for (var i=0; i<n; i++) {
		array.push(values.next().value);
	}
	return array;
}

class EnumSet {
	constructor(enumHead) { 
	  this.set = new Set(enumHead);
	  this.enumHead = S.isUndefined(enumHead)?[]:enumHead;	
	}
	add(e) { this.set.add(e) }
	has(e) { return this.set.has(e) }
	sample(n) { 
	  if (S.isUndefined(n))
			return S.sample(this.enumHead);
		else {
			var a=[];
			for (var i=0; i<n; i++) a.push(this.sample());
			return a;
		}
	}
	enumerate() { return this.enumHead }
	intersection(y) {
		var x=this, xy=new EnumSet();
		xy.has=function(e) { return x.has(e)&&y.has(e) }
		return xy;
	}
	union(y) {
		var x=this, xy=new EnumSet();
		xy.has=function(e) { return x.has(e)||y.has(e) }
		return xy;
	}
	difference(y) {
		var x=this, xy=new EnumSet();
		xy.has=function(e) { return x.has(e)&&!y.has(e) }
		return xy;
	}
	symmetricDifference(y) {
		var x=this;
		return x.union(y).difference(x.intersection(y));
	}
	cartesianProduct(y) { 
		var x=this, xy=new EnumSet();
		xy.has=function(e) { return x.has(e[0]) && y.has(e[1]) }
		return xy;
	}
}

S.Set = EnumSet

function steps(a,b,step) {
	var array=[];
	for (var i=a; i<=b; i+=step) 
		array.push(i);
	return array;
}

var enumFloat = [-3.2,-1, 0, 1, 2.3, 4.7];
var enumInt   = [-10,-5,-1,0,1,3,5,6];
var enumN0    = steps(0,10,1);
var enumN1    = steps(1,10,1);
var enumOdd   = steps(1,15,2);
var enumEven  = steps(2,15,2);
var enumPrime = [2,3,5,7,11,13,17,19,23,29,31,37];
var enumAll   = ["hi", 5, Math.PI, EnumSet, S.isBool, enumPrime, new Set() ];

// 全體集合
S.All = new EnumSet(enumAll);
S.All.has = S.yes;

// 空集合
S.Empty = new EnumSet([]);
S.Empty.has = S.no;

// 浮點數集合
S.Float=new EnumSet(enumFloat);
S.Float.has=S.isFloat;

// 整數集合
S.Z=S.Integer=new EnumSet(enumInt);
S.Z.has=S.isInteger;

// 自然數集合 N0
S.N0=new EnumSet(enumN0);
S.N0.has=(e)=>S.isInteger(e)&&e>=0;

// 自然數集合 N1
S.N1=new EnumSet(enumN1);
S.N1.has=(e)=>S.isInteger(e)&&e>1;

// 偶數集合
S.Even=new EnumSet(enumEven);
S.Even.has=S.isEven;

// 奇數集合
S.Odd=new EnumSet(enumOdd);
S.Odd.has=S.isOdd;

// 質數集合
S.Prime=new EnumSet(enumPrime)
S.Prime.has=I.isPrime;

// 有限集合 0...n-1
S.Finite=(n)=>new EnumSet(steps(0,n-1,1));

// 羅素集合悖論
S.RussellSet=new EnumSet(enumAll);
S.RussellSet.has=function(e) { return !e.has(e) }

module.exports=S;

接著我們示範如何使用這個模組:

檔案: example/setEx.js

var R = require("../rlab");

var S10 = R.Finite(10);
print('S10.sample(5)=', S10.sample(5));
print('Float.sample(5)=', R.Float.sample(5));
print('Z.sample(5)=', R.Z.sample(5));
print('Even.sample(5)=', R.Even.sample(5));
print('Odd.sample(5)=', R.Odd.sample(5));
print('Prime.sample(5)=', R.Prime.sample(5));
print('Prime.enumerate()=', R.Prime.enumerate());
print('Empty.sample(5)=', R.Empty.sample(5));
var OddPrime = R.Odd.intersection(R.Prime);
OddPrime.enumHead = [3,5,7,11,13];
print('OddPrime.sample(5)=', OddPrime.sample(5));
print('OddPrime.has(71)=', OddPrime.has(71));
print('OddPrime.has(70)=', OddPrime.has(70));
print('OddPrime.has(69)=', OddPrime.has(69));
var OddXPrime = R.Odd.cartesianProduct(R.Prime);
print('OddXPrime.has([9,71])=', OddXPrime.has([9, 71]));
print('OddXPrime.has([8,71])=', OddXPrime.has([8, 71]));
print('OddXPrime.has(71)=', OddXPrime.has(71));
// RussellSet
print('RussellSet.has(Odd)=', R.RussellSet.has(R.Odd));
print('RussellSet.has(RussellSet)=', R.RussellSet.has(R.RussellSet));

執行結果:

D:\Dropbox\github\rlab\example>node setEx.js
S10.sample(5)= [ 1, 3, 9, 6, 3 ]
Float.sample(5)= [ 0, 4.7, 4.7, 4.7, 1 ]
Z.sample(5)= [ 5, 3, -10, 6, 5 ]
Even.sample(5)= [ 8, 14, 10, 6, 8 ]
Odd.sample(5)= [ 1, 11, 1, 1, 3 ]
Prime.sample(5)= [ 2, 29, 3, 31, 5 ]
Prime.enumerate()= [ 2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37 ]
Empty.sample(5)= [ undefined, undefined, undefined, undefined, undefined ]
OddPrime.sample(5)= [ 7, 13, 7, 5, 7 ]
OddPrime.has(71)= true
OddPrime.has(70)= false
OddPrime.has(69)= false
OddXPrime.has([9,71])= true
OddXPrime.has([8,71])= false
OddXPrime.has(71)= false
RussellSet.has(Odd)= true
D:\Dropbox\github\rlab\lib\set.js:216
S.RussellSet.has=function(e) { return !e.has(e) }
                         ^

RangeError: Maximum call stack size exceeded
    at EnumSet.S.RussellSet.has (D:\Dropbox\github\rlab\lib\set.js:216:26)
    at EnumSet.S.RussellSet.has (D:\Dropbox\github\rlab\lib\set.js:216:42)
    at EnumSet.S.RussellSet.has (D:\Dropbox\github\rlab\lib\set.js:216:42)
    at EnumSet.S.RussellSet.has (D:\Dropbox\github\rlab\lib\set.js:216:42)
    at EnumSet.S.RussellSet.has (D:\Dropbox\github\rlab\lib\set.js:216:42)
    at EnumSet.S.RussellSet.has (D:\Dropbox\github\rlab\lib\set.js:216:42)
    at EnumSet.S.RussellSet.has (D:\Dropbox\github\rlab\lib\set.js:216:42)
    at EnumSet.S.RussellSet.has (D:\Dropbox\github\rlab\lib\set.js:216:42)
    at EnumSet.S.RussellSet.has (D:\Dropbox\github\rlab\lib\set.js:216:42)
    at EnumSet.S.RussellSet.has (D:\Dropbox\github\rlab\lib\set.js:216:42)

小結

集合論是所有數學的基礎建設,但由於可能導致《悖論》的關係,因此數學上必須小心建構。

有些《悖論》的原因是我們想像力還沒到那種境界,像是《畢達哥拉斯學派》認為所有數都可以寫成 p/q 這種形式的《有理數》,沒辦法將想像力從《有理數》提升到《無理數》,就是一個經典的範例!

同樣的、從《實數》到《複數》的路上,也必須先承認 $\sqrt{-1}$ 是有意義的數字,才能夠釋放出那些對圓周運動非常有用的數學理論,《複數》在電磁學和訊號處理上的用途,是非常令人驚豔的!

另外的一些《悖論》,則是由於《數學基礎定義得不夠嚴謹》而產生的,像是《無限小》的概念經過《柯西》的《分析學》之後,就形成了一個相當嚴密的數學體系。

還有一些《悖論》,標示著《某些事物能力的極限》,像是羅素的《集合悖論、理髮師悖論》,還有《圖靈的停止問題、哥德爾不完備定理》等等,這些問題通常是我們無法 100% 完全正確解決的。

但是、這並不代表我們對這些問題完全無能為力。舉例而言、電腦的防毒問題,也就是判斷一個程式是否為病毒的問題,應該也是個《無法完全正確解決》的問題,但是我們還是可以寫出《檢查病毒》的程式,雖然無法100%正確,但是只要正確率夠高,對我們還是會很有幫助的!

《集合論》在經過羅素的《集合悖論、理髮師悖論》之後,被 Cantor 與後繼者改造成為《公理化集合論》,並透過《一階邏輯》進行了嚴密的定義,這讓集合論裏不會出現《自相矛盾集合》的現象,但是這並不代表所有問題都可以被解決了,一旦加上《數與加減乘除》等運算之後,就會發現還是有些問題是無法解決的。

像是《哥德爾不完備定理》中就證明了一個命題:《在涵蓋數論體系的數學世界中,一致性和完備性是無法兼得的》。

因此、有些定理是對的,但是我們證明不出來,萬一我們真的證明出來了,而且那個證明是對的,這時請先別高興,因為這代表你的公理系統已經出錯了!(整個數學大廈已然倒塌 ….)

以上就是我們從《電腦》的角度來看集合論的一些想法,或許很少《科學計算》與《數值分析》的書會談論這樣的主題,但是這本書希望盡可能涵蓋更多的《數學、科學與程式》之間的主題,因此您將會在後續的章節當中,繼續看到很多類似這樣的數學科學主題,而這也是筆者寫這本書的主要出發點!