用SQL解一道有趣的數學題:Gauss和Poincare
楊廷琨,網名 yangtingkun
雲和恩墨技術總監,Oracle ACE Director,ACOUG 核心專家
用SQL為解析一道數學題
Oracle的SQL語句功能強大,它可以實現一些你意想不到的功能。比如下面這個數學問題,如果使用常規的數學方法進行手工分析將會十分麻煩,而使用SQL來求解則要簡單得多。且看楊廷琨用一個SQL解析一道數學難題。
問題提出
這是一個流傳已久的故事:
Gauss和Poincare在天堂相遇了,上帝說:你們都是人間最偉大的數學家,那我來出道題考考你們誰更聰明。我在左手寫一個大於1小於100的數,在右手同樣寫一個大於1小於100的數,然後把他們的和寫在Gauss手上,把積寫在Poincare手上,看看你們能不能猜出這兩個數字是幾。
Gauss看了手上的數字,說:“我不知道這兩個數字是幾,可我保證Poincare也不知道。”
Poincare看了手上的數字,說:“我原來的確不知道那兩個數字是幾,可我現在知道了。”
Gauss說:“那我也知道了。”
問題:那兩個數字是幾?
以下是來自維基百科關於兩位數學家的簡要介紹
約翰·卡爾·弗里德里希·高斯(英語:Gauss;1777年4月30日-1855年2月23日), 德國著名數學家、物理學家、天文學家、大地測量學家。高斯被認為是歷史上最重要的數學家之一,並有“數學王子”的美譽。 高斯獨立發現了二項式定理的一般形式、數論上的“二次互反律”、素數定理、及算術-幾何平均數。1796年,19歲的高斯得到了一個數學史上極重要的結果,就是《正十七邊形尺規作圖之理論與方法》。
儒勒·昂利·龐加萊(法語:Jules Henri Poincaré,1854年4月29日—1912年7月17日),法國最偉大的數學家之一,理論科學家和科學哲學家。龐加萊被公認是19世紀後和20世紀初的領袖數學家,是繼高斯之後對於數學及其應用具有全面知識的最後一個人。 他對數學,數學物理,和天體力學做出了很多創造性的基礎性的貢獻。
問題分析
這道題給出的已知條件十分隱蔽,首先來分析一下已知條件。
根據題意,最終所求的是兩個數字,而這兩個數字的範圍是在1和100之間。題目中明確說明的條件僅此而已,剩下的條件就要依靠分析才能得出了。
對於Gauss來說他知道兩個數之和,而不知道兩個數的積,但是Gauss卻肯定的說,他保證Poincare也不知道這兩個數是什麼。這句話就很有深意。
舉個例子,如果兩個數分別是3和7,那麼這兩個數之積就是21。由於這兩個數都是大於1的,因此兩個數乘積為21的只有3和7這一種可能。如果Poincare手中的值是21,那麼Poincare肯定可以確定這兩個數是什麼,因此對於Gauss而言,手中的值肯定不可能是10(3和7這兩個數的和)。如果這個值是10,那麼這兩個數就有可能是3和7,而3和7的乘積又是唯一的,所以Gauss就無法確定Poincare也不知道結果。
當然這只是舉了一個例子,如果歸納一下就是說,對於Gauss而言,所有滿足相加與手中數值相等的兩個數,它們的積都不是唯一的。只有滿足這個條件,他才能確認Poincare不知道這兩個數是什麼。
對於Poincare來說,他開始並不知道兩個數是什麼,但是Gauss說出了他的推斷之後,Poincare居然知道了這兩個數是什麼。這說明由於Gauss剛才的推斷所排除的一些數值組合後,使得Poincare手中的積可以唯一確定這兩個數值了。
隨後Gauss說他也知道了,意味著根據Poincare能夠確認這兩個數的資訊,Gauss也可以唯一的確定這兩個數了。
SQL解答
題目已經分析完了,那麼如何用SQL來求解呢,為了便於描述,這裡先給出最終的結果,然後描述一下這個SQL的思路如下:
SQL> WITH T_NUM AS
2 (SELECT ROWNUM + 1 NUM FROM DUAL CONNECT BY LEVEL < 99)
3 SELECT A, B
4 FROM
5 (
6 SELECT
7 A,
8 B,
9 TOTAL,
10 MUL,
11 MUL_P,
12 COUNT(DECODE(MUL_P, 1, 1)) OVER(PARTITION BY TOTAL) VALUE
13 FROM
14 (
15 SELECT
16 A,
17 B,
18 TOTAL,
19 MUL,
20 COUNT(*) OVER (PARTITION BY TOTAL) TOTAL_P,
21 COUNT(*) OVER (PARTITION BY MUL) MUL_P
22 FROM
23 (
24 SELECT
25 A,
26 B,
27 TOTAL,
28 MUL,
29 MIN(MUL_P) OVER (PARTITION BY TOTAL) MUL_M
30 FROM
31 (
32 SELECT
33 A.NUM A,
34 B.NUM B,
35 A.NUM + B.NUM TOTAL,
36 A.NUM * B.NUM MUL,
37 COUNT(*) OVER (PARTITION BY A.NUM + B.NUM) TOTAL_P,
38 COUNT(*) OVER (PARTITION BY A.NUM * B.NUM) MUL_P
39 FROM T_NUM A, T_NUM B
40 WHERE A.NUM < B.NUM
41 )
42 )
43 WHERE MUL_M != 1
44 )
45 )
46 WHERE MUL_P = 1
47 AND VALUE = 1;
A B
---------- ----------
4 13
SQL有點長,下面簡單分析一下。
首先看一下WITH語句,這個語句其實就是構造大於1小於100的所有的數。
31 (
32 SELECT
33 A.NUM A,
34 B.NUM B,
35 A.NUM + B.NUM TOTAL,
36 A.NUM * B.NUM MUL,
37 COUNT(*) OVER (PARTITION BY A.NUM + B.NUM) TOTAL_P,
38 COUNT(*) OVER (PARTITION BY A.NUM * B.NUM) MUL_P
39 FROM T_NUM A, T_NUM B
40 WHERE A.NUM < B.NUM
41 )
首先從SQL的最內層開始分析,這一層很簡單,構造符合大於1小於100的兩個數的笛卡兒積,得到所有的可能性。
根據題目的描述,第一個數是2,第二個數是3的情況,與第一個數是3,第二個數是2沒有區別,所以這層SQL在連線時加上了限制條件A > B,這樣可以去掉重複的結果。在SELECT列表中分別列出A、B兩個數值,以及兩個數值之和(A+B)、兩個數值之積(A*B),還通過分析函式計算所有可能性中兩個數之和與當前兩個數之和相等的組合的個數,以及所有可能性中兩個數之積與當前兩個數之積相等的組合的個數。
23 (
24 SELECT
25 A,
26 B,
27 TOTAL,
28 MUL,
29 MIN(MUL_P) OVER (PARTITION BY TOTAL) MUL_M
30 FROM
31 (
32 SELECT
33 A.NUM A,
34 B.NUM B,
35 A.NUM + B.NUM TOTAL,
36 A.NUM * B.NUM MUL,
37 COUNT(*) OVER (PARTITION BY A.NUM + B.NUM) TOTAL_P,
38 COUNT(*) OVER (PARTITION BY A.NUM * B.NUM) MUL_P
39 FROM T_NUM A, T_NUM B
40 WHERE A.NUM < B.NUM
41 )
42 )
接著看第二層SQL,除了列出A和B兩個數外,還列出了A和B之和、A和B之積以及一個很重要的值:根據兩個數之和進行分組,找出這兩個數之積的組合的最小個數。這樣描述確實很抽象,不過沒有關係,馬上要分析的第三層,會對這個值的含義做進一步的說明。
14 (
15 SELECT
16 A,
17 B,
18 TOTAL,
19 MUL,
20 COUNT(*) OVER (PARTITION BY TOTAL) TOTAL_P,
21 COUNT(*) OVER (PARTITION BY MUL) MUL_P
22 FROM
23 (
24 SELECT
25 A,
26 B,
27 TOTAL,
28 MUL,
29 MIN(MUL_P) OVER (PARTITION BY TOTAL) MUL_M
30 FROM
31 (
32 SELECT
33 A.NUM A,
34 B.NUM B,
35 A.NUM + B.NUM TOTAL,
36 A.NUM * B.NUM MUL,
37 COUNT(*) OVER (PARTITION BY A.NUM + B.NUM) TOTAL_P,
38 COUNT(*) OVER (PARTITION BY A.NUM * B.NUM) MUL_P
39 FROM T_NUM A, T_NUM B
40 WHERE A.NUM < B.NUM
41 )
42 )
43 WHERE MUL_M != 1
44 )
第三次列出的仍然包括A和B兩個數,以及兩個數之和、兩個數之積。除此之外,還列出了與當前記錄中兩個數之和相同的組合數;與當前記錄中兩個數之積相同的組合數,更關鍵的是這裡進行了過濾,在第二層得到的MUL_M不等於1。
目前得到的結果就是Gauss雖然自己不知道兩個數是什麼,但是可以確認Poincare也不知道。前面已經舉過3和7的例子來說明這個問題了,這裡就不再重複了。如果歸納起來,就是Gauss手中的兩數的和,分解為任何一種可能所得到的兩個數的積,都不是唯一的。在SQL中表示的結果就是MIN(MUL_P) OVER (PARTITION BY TOTAL) != 1。
隨後要解決的問題就是Poincare說他原來並不知道兩個數分別是什麼,而當Gauss說完那句話後他已經知道這兩個數的值了。也就是說到目前為止,兩個數的積已經可以唯一確定這兩個數是什麼了,數學描述就是兩個數乘積分組後值相同的個數是1。在SQL中的表示也就是最外層SQL的限制條件MUL_P = 1。
隨後還有最後一個條件,就是Gauss這時也知道了兩個數是什麼,說明Gauss根據Poincare確定兩個數的數值這個事實,進一步推斷出了最終的結果。這個SQL的實現是通過COUNT(DECODE(MUL_P, 1, 1)) OVER(PARTITION BY TOTAL) = 1來實現的。前面提到了,只有MUL_P為1的情況,Poincare才能唯一確定兩個數的值,而Gauss根據這個結果也推斷出兩個數的值,說明在當前兩個數之和的分組中,只有一種情況滿足MUL_P的值為1。
因此整個SQL組合起來,就是這道題的解。
這道題本身解決問題的思路只有窮舉法,如果手工計算會非常麻煩且容易出錯,而利用窮舉法解決問題正是SQL所擅長的。將數學語句通過SQL來進行表述,就可以方便快速地得到最終的結果。
近期文章
從Approx_Count_Distinct到M7的CPU整合
Cloud時代DBA的DevOps最佳實踐 - SQL 稽核
資料驅動,成就未來。整合業界頂尖的技術與合作伙伴資源,圍繞資料及相關領域,提供解決方案和專業服務。
業務架構
電子渠道(網路銷售)分析系統、資料治理
IT基礎架構
分散式儲存解決方案
資料架構
Oracle DB2 MySQL NoSQL
專項服務:架構/安全/容災/優化/整合/升級/遷移
運維服務:運維服務 代維服務
人才培養:個人認證 企業內訓
軟體產品:SQL稽核、監控、資料恢復
應用架構
應用軟體和中介軟體:資料建模 | SQL稽核和優化 | 中介軟體服務