您好,登錄后才能下訂單哦!
在談集合之前,需要先談?wù)勲x散性的概念:
所謂離散性,是指集合的成員可以游離在集合之外存在并參與運(yùn)算,游離成員還可以再組成新的集合。從離散性的解釋上可以知道,離散性是針對(duì)集合而言的一種能力,離開集合概念單獨(dú)談離散性就沒(méi)有意義了。
離散性是個(gè)很簡(jiǎn)單的特性,幾乎所有支持結(jié)構(gòu)(對(duì)象)的高級(jí)語(yǔ)言都天然支持,比如我們用Java時(shí)都可以把數(shù)組成員取出來(lái)單獨(dú)計(jì)算,也可以再次組成新的數(shù)組進(jìn)行集合運(yùn)算(不過(guò)Java幾乎沒(méi)有提供集合運(yùn)算類庫(kù))。
打個(gè)通俗的比方:假設(shè)有一個(gè)盒子里裝滿了白色小球,針對(duì)離散性的操作就相當(dāng)于把盒子打開,把里面的小球一個(gè)個(gè)單獨(dú)拿出來(lái)刷上不同顏色,則操作后每個(gè)小球的顏色都各不相同;而針對(duì)整個(gè)集合的操作,就相當(dāng)于把裝入一定數(shù)量小球的盒子,運(yùn)到某個(gè)地方,則盒內(nèi)所有小球也都同時(shí)被運(yùn)到了那個(gè)地方。
回到程序的編寫方向上,同時(shí)具備良好的集合運(yùn)算類庫(kù)與離散性引用機(jī)制的集算器腳本語(yǔ)言,相較于傳統(tǒng)的SQL語(yǔ)言(受限于關(guān)系代數(shù)),無(wú)論從思考方式還是從執(zhí)行效率上來(lái)看,都有著先天的優(yōu)勢(shì)。
比如以前提到的:計(jì)算至少連漲四天的股票,在至少連漲三天的股票中所占的比例:
一個(gè)比較普通的思路是用窗口函數(shù):將數(shù)據(jù)按公司名分區(qū)后再按日期排序(Order By),調(diào)用LAG窗口函數(shù)向上做求差運(yùn)算并根據(jù)是否為負(fù)記是否為NULL,調(diào)用LAG和LEAD窗口函數(shù)找出上升趨勢(shì)和下降趨勢(shì)的分段點(diǎn)并記1,再調(diào)用SUM窗口函數(shù)將分段點(diǎn)預(yù)設(shè)值累加從而成為分段的依據(jù)字段,然后清空之前用NULL標(biāo)記的無(wú)效行后,再分別統(tǒng)計(jì)算出>=3和>=4的數(shù)目,最后算出一個(gè)比值。
具體實(shí)現(xiàn)代碼如下(下面以SqlServer數(shù)據(jù)庫(kù)為例):
WITH T1 AS
(
SELECT T.COM COM, T.STA STA, SUM(T.FLG) OVER(PARTITION BY T.COM ORDER BY T.DAT) GRP
FROM (
SELECT [Company] COM, [Date] DAT, [Price] PRI,
CASE WHEN [Price] > LAG([Price],1,0) OVER(PARTITION BY [Company] ORDER BY [Date])
THEN 1 ELSE NULL END STA,
CASE WHEN [Price] < LAG([Price],1,0) OVER(PARTITION BY [Company] ORDER BY [Date])
AND [Price] < LEAD([Price],1,9999999) OVER(PARTITION BY [Company] ORDER BY [Date])
THEN 1 ELSE 0 END FLG
FROM Stock
) T
),
T2 AS
(
SELECT T1.COM COM, T1.GRP GRP, COUNT(T1.COM) CNT FROM T1 WHERE T1.STA IS NOT NULL GROUP BY T1.COM, T1.GRP
),
T3 AS
(
SELECT COUNT(T2.COM) Up3Days FROM T2 WHERE T2.CNT >= 3
),
T4 AS
(
SELECT COUNT(T2.COM) Up4Days FROM T2 WHERE T2.CNT >= 4
)
SELECT CONVERT(FLOAT,T4.Up4Days,120)/CONVERT(FLOAT,T3.Up3Days,120) FROM T3 JOIN T4 ON 1=1
可以看出:這種方法在數(shù)據(jù)處理的過(guò)程中,對(duì)數(shù)據(jù)增加分類的定義與處理,實(shí)在太麻煩:除了幾層的嵌套子查詢,還得增加過(guò)濾和分段的標(biāo)記、還得思考如何用分段標(biāo)記形成分段字段,還得思考如何不重復(fù)查詢同一個(gè)表浪費(fèi)時(shí)間……那么有沒(méi)有更靈活的方法呢?也許有,比如對(duì)于SqlServer還可以考慮使用游標(biāo)等方法(雖然靈活不過(guò)代碼量只怕更多……感覺(jué)T-SQL正無(wú)限接近Java中)
CREATE TABLE #RT(Company VARCHAR(20) PRIMARY KEY NOT NULL, Price DECIMAL NOT NULL, Record INT NULL, Most INT NULL)
CREATE TABLE #TT(Company VARCHAR(20) NOT NULL, Price DECIMAL NOT NULL, DT DATE NOT NULL)
CREATE CLUSTERED INDEX IDX_#TT ON #TT(Company,DT) –SQLSVR2016需要?jiǎng)?chuàng)建索引否則排序無(wú)效
INSERT INTO #TT SELECT [Company], [Price], [Date] FROM Stock ORDER BY [Company],[Date]
DECLARE @Company VARCHAR(20), @Price DECIMAL, @Record INT, @Most INT
SET @Price=0 –Price字段需要有初始值0
DECLARE iCursor CURSOR FOR SELECT Company, Price FROM #TT –定義游標(biāo)
OPEN iCursor –開啟游標(biāo)
FETCH NEXT FROM iCursor INTO @Company, @Price –取第一行數(shù)據(jù)存入變量
WHILE @@FETCH_STATUS=0 –游標(biāo)取數(shù)成功則進(jìn)入循環(huán)
BEGIN
IF((SELECT COUNT(*) FROM #RT WHERE Company=@Company)=0)
BEGIN INSERT INTO #RT VALUES(@Company, @Price, 1, 1) END
ELSE
BEGIN
IF((SELECT TOP 1 Price FROM #RT WHERE Company=@Company)<@Price)
BEGIN
SET @Record = 1+(SELECT TOP 1 Record FROM #RT WHERE Company=@Company)
SET @Most = (SELECT TOP 1 Most FROM #RT WHERE Company=@Company)
UPDATE #RT SET Price=@Price, Record=@Record WHERE Company=@Company
IF(@Record>=3 AND @Most<@Record)
BEGIN UPDATE #RT SET Most=@Record WHERE Company=@Company END
END
ELSE
BEGIN UPDATE #RT SET Price=@Price, Record=1 WHERE Company=@Company END
END
FETCH NEXT FROM iCursor INTO @Company, @Price –繼續(xù)取下一條數(shù)據(jù)否則會(huì)死循環(huán)
END
CLOSE iCursor –關(guān)閉游標(biāo)
DEALLOCATE iCursor –釋放游標(biāo)內(nèi)存
; –注意此處要用分號(hào)結(jié)尾否則WITH子句會(huì)報(bào)錯(cuò)
WITH T1 AS (SELECT COUNT(*) Num FROM #RT WHERE #RT.Most>=3),
T2 AS (SELECT COUNT(*) Num FROM #RT WHERE #RT.Most>=4)
SELECT CONVERT(FLOAT,T2.Num,120)/CONVERT(FLOAT,T1.Num,120) FROM T1 JOIN T2 ON 1=1 –計(jì)算最終結(jié)果
DROP TABLE #RT
DROP TABLE #TT
而且這樣的寫法基本上并不具有通用性,也就是說(shuō)如果換個(gè)數(shù)據(jù)庫(kù),那你可能還需要再研究一次別的數(shù)據(jù)庫(kù)中使用游標(biāo)的方法。
再來(lái)看看集算器要搞定類似問(wèn)題時(shí)需要的代碼(為了方便起見(jiàn)數(shù)據(jù)源使用的Excel):
A | |
1 | =file(“E:/Stock.xlsx”).xlsimport@t().sort(Date).group(Company) |
2 | =A1.((a=0,~.max(a=if(Price>Price[-1],a+1,0)))) |
3 | =string(A2.count(~>=4)/A2.count(~>=3),”0.00%”) |
實(shí)現(xiàn)一個(gè)同樣的目標(biāo),相比之下,集算器的代碼不僅簡(jiǎn)潔、高效,而且適應(yīng)性廣,另外即使需要針對(duì)大數(shù)據(jù)量做特殊的并行計(jì)算處理時(shí),也不會(huì)束手無(wú)策。
既然數(shù)據(jù)庫(kù)SQL語(yǔ)言編程受到的限制這么多,寫起來(lái)這么麻煩,那么存在數(shù)據(jù)庫(kù)中的數(shù)據(jù),難道就沒(méi)法整治了嗎?
當(dāng)然不是,畢竟我們還有集算器這一法寶。下面再來(lái)看一個(gè)簡(jiǎn)單的計(jì)算:
如何對(duì)一個(gè)字段循環(huán)求和,當(dāng)滿足一個(gè)值(80)就退出循環(huán),并能得到最后一次循環(huán)時(shí)各字段對(duì)應(yīng)的值
SqlServer的腳本程序如下:
with cte as (
select *,cnt3 sumcnt from Tb where cnt1=1
union all
select Tb.*, sumcnt+Tb.cnt3 from Tb join cte on 1+cte.cnt1=Tb.cnt1 where sumcnt+Tb.cnt3<=80
) select * from Tb where cnt1 = (select max(cnt1) from cte)
用上了with as子句的遞歸功能,這樣確實(shí)可以在數(shù)據(jù)行數(shù)過(guò)多時(shí),提前結(jié)束不必要的計(jì)算,節(jié)省了計(jì)算時(shí)間。但With As子句的遞歸在更復(fù)雜的應(yīng)用中,還是比較難寫的,畢竟稍不注意就可能陷入無(wú)限死循環(huán);而且說(shuō)實(shí)話,有些數(shù)據(jù)庫(kù)可能也不支持with as子句的遞歸功能
還有另一種:
select top 1 cnt1, cnt3 from
(
select cnt1 cnt1, cnt3 cnt3, (select SUM(cnt3) from Tb b where b.cnt1<=a.cnt1) cnt_sum from Tb a
) c where cnt_sum<=80 order by cnt_sum desc
這個(gè)表面上看起來(lái)只用了兩次子查詢,但最里面的子查詢執(zhí)行邏輯并不是很好理解,其實(shí)它利用了SqlServer數(shù)據(jù)庫(kù)底層對(duì)select執(zhí)行流程的細(xì)節(jié):先select出a表的cnt1和cnt3,然后在最里面那個(gè)子查詢中根據(jù)a表的cnt1對(duì)b表做where子句過(guò)濾后,再計(jì)算b表cnt3的sum聚合值。而這就要求數(shù)據(jù)庫(kù)執(zhí)行語(yǔ)句順序,必須確實(shí)是按照設(shè)計(jì)者的思維去執(zhí)行,否則便可能出錯(cuò)或無(wú)法識(shí)別。因此這個(gè)方法也未必能夠適用于所有數(shù)據(jù)庫(kù)。
當(dāng)然,以上兩種SQL腳本運(yùn)行結(jié)果在SqlServer上還是一樣的:
然后,我們?cè)賮?lái)看看集算器的辦法:
A | |
1 | =connect(“SQLSVR”).query(“select * from Tb”) |
2 | =A1.iterate((x=~[-1],~~+cnt3),0,~~>80) |
其中變量x就是要計(jì)算的結(jié)果
解釋一下:A1中的代碼是從SqlServer數(shù)據(jù)庫(kù)中取數(shù)并建立序表對(duì)象,具體細(xì)節(jié)就不多說(shuō)了,按照集算器自帶教程,照貓畫虎的操作就可以搞定。
真正發(fā)揮計(jì)算作用的是A2行的iterate函數(shù),看起來(lái)感覺(jué)有點(diǎn)迷糊?恐怕那只是因?yàn)槟惚容^習(xí)慣SQL而已。下面讓我來(lái)告訴你這個(gè)函數(shù)用起來(lái)有多么簡(jiǎn)單。
iterate函數(shù)是一個(gè)循環(huán)函數(shù),所謂循環(huán)函數(shù)就是會(huì)根據(jù)調(diào)用的他的序列或序表中所包含的元素個(gè)數(shù)決定最大循環(huán)次數(shù)。說(shuō)的簡(jiǎn)單點(diǎn),你可以把它想象成一個(gè)更加靈活的while循環(huán):
iterate函數(shù)共有三個(gè)參數(shù),這里可記為iterate(a,b,c),還包含一個(gè)用于保存每次循環(huán)計(jì)算得到結(jié)果的隱藏變量:~~,以及一個(gè)指向當(dāng)前序列元素或序表記錄的類似于指針的變量:~。
iterate(a,b,c)的調(diào)用順序是b->a->c,其中b用于賦予計(jì)算結(jié)果變量~~一個(gè)初值,a則是每次循環(huán)都會(huì)計(jì)算參數(shù)表達(dá)式并賦值~~,c則是一個(gè)布爾表達(dá)式,當(dāng)表達(dá)式的值為真時(shí)函數(shù)會(huì)提前結(jié)束循環(huán)。(注意:是為真時(shí)退出循環(huán))
說(shuō)白了iterate(a,b,c)就相當(dāng)于下面用while循環(huán)模擬的偽代碼的示意(注意~和~~是變量,a、b、c是表達(dá)式):
i = 0;
~~ = b;
while (i <= len && !c) {
~ = A(++i);
~~ = a;
}
怎么樣,看完是不是覺(jué)得渾身一陣清爽:原來(lái)編程其實(shí)可以這么簡(jiǎn)單!
既然離散性高,語(yǔ)法靈活,好處有這么多,那么是否就可以一味的追求離散性,而忽視集合運(yùn)算的重要?當(dāng)然也不是。
離散性高,雖然讓編程語(yǔ)言(比如Java,更甚者如C++)語(yǔ)法靈活,解決復(fù)雜問(wèn)題時(shí),也比離散性差的語(yǔ)言(比如SQL,其次如Python)優(yōu)勢(shì)明顯。但在解決常見(jiàn)的簡(jiǎn)單問(wèn)題,尤其是某一限定領(lǐng)域的問(wèn)題時(shí),更專業(yè)化的語(yǔ)言往往比適應(yīng)面更廣的語(yǔ)言,更能讓編程人員快速高效地開發(fā)出有效的代碼來(lái)。這在當(dāng)前社會(huì)追求各種工程的效率的環(huán)境下,更顯得尤為重要。
比如最簡(jiǎn)單的例子:讀一張Excel表,計(jì)算一下按分組字段(STYLE,BEDROOM)分組后,另一數(shù)值字段(Price)的平均值
用集算器算的話,非常簡(jiǎn)便
A | |
1 | =file(“D:/data.xlsx”).xlsimport@tc() |
2 | =A1.groups(STYLE,BEDROOMS;avg(SQFEET):SQFEET,avg(BATHS):BATHS,avg(PRICE):PRICE) |
當(dāng)然與集算器有點(diǎn)類似的python,也有這類的運(yùn)算庫(kù),這恐怕也是python最近異常火爆的原因之一
import pandas as pd
data = pd.read_excel(‘D:/data.xlsx’,sheet_name=0)
print(data.groupby([‘STYLE’,‘BEDROOMS’]).mean())
但是……你能想象用沒(méi)有提供類似集合運(yùn)算的類庫(kù)的Java甚至C++,來(lái)實(shí)現(xiàn)同樣的功能嗎?只怕光是讀Excel都?jí)蜃鰝€(gè)模塊了,然后是分組與聚合的計(jì)算,還有報(bào)表對(duì)象的構(gòu)建,甚至運(yùn)算結(jié)果的顯示功能(單單想象一下都感覺(jué)很累)正因?yàn)槿绱?,集算器的語(yǔ)法在設(shè)計(jì)之初,就考慮到了集合性運(yùn)算與離散性引用,這對(duì)看似矛盾卻缺一不可的客觀需求。如果說(shuō)武功的最高境界乃是陰陽(yáng)互濟(jì)的話,那么編程語(yǔ)言的最高境界,我覺(jué)得恐怕就是集合與離散的優(yōu)點(diǎn)兼而有之吧。
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。