領(lǐng)域建模在于不斷挖掘領(lǐng)域的本質(zhì)广料,然后用優(yōu)秀的代碼簡潔地表現(xiàn)出來。而函數(shù)式非常適合將領(lǐng)域映射到數(shù)學(xué)本質(zhì)上幼驶。前一陣子學(xué)習(xí)erlang艾杏,用erlang做了一些練習(xí),分享其中的一個(gè)盅藻。
題目: 數(shù)數(shù)如下圖形中一共包含多少三角形购桑?
答案是24,你數(shù)對了嗎氏淑?回憶一下你剛才是怎么數(shù)的其兴?
這道題如果由人來數(shù),每個(gè)人數(shù)法各異夸政!但是如果要交給計(jì)算機(jī)來做元旬,就必須將數(shù)的方法描述成計(jì)算機(jī)可以執(zhí)行的形式化算法。這種描述方法可以有非常多種守问,而我們希望找到一套抽象層次高的和領(lǐng)域非常貼切的描述匀归,以降低后續(xù)理解、維護(hù)成本耗帕。
對于該問題穆端,我們首先站在領(lǐng)域角度定義什么是三角形?
triangle([A, B, C]) ->
connected(A, B) andalso
connected(B, C) andalso
connected(C, D) andalso
(not in_same_line(A, B, C)).
如上仿便,我們定義了一個(gè)三角形就是三個(gè)點(diǎn)体啰,兩兩相連攒巍,但是三個(gè)點(diǎn)不同時(shí)在一條直線上。
對于如上描述荒勇,關(guān)鍵是如何定義connected
和in_same_line
柒莉。
對于connected
,就是兩個(gè)點(diǎn)同時(shí)在一條直線連接上沽翔。那么什么是一條直線連接兢孝?
由于我們關(guān)注的是點(diǎn)在線上的關(guān)系,所以我們定義一條直線為在直線上所有點(diǎn)得集合仅偎。
例如對以上例跨蟹,我們存在直線:[a, b, h]
,[a, c, g, i]
等等。
我們把上圖中所有直線用erlang描述出來:
lines() ->
["abh", "acgi", "adfj", "aek", "bcde", "hgfe", "hijk"].
由于我們用單字符表示點(diǎn)橘沥,而字符串在erlang中實(shí)際就是list窗轩,所以我們將一條直線簡寫為在線上所有點(diǎn)的字符的字符串。
有了對直線的定義座咆,接下來品姓,一個(gè)點(diǎn)是否在直線上,那就是元素與集合的屬于關(guān)系箫措;而兩個(gè)點(diǎn)是否相連就是集合與集合之間的包含關(guān)系。
subset([], _S) -> true;
subset([H|T], S) ->
lists:member(H, S) andalso subset(T, S).
belong(_, []) -> false;
belong(S, [H|T]) -> subset(S, H) orelse belong(S, T).
connected(P1, P2) -> belong([P1, P2], lines()).
in_same_line(P1, P2, P3) -> belong([P1, P2, P3], lines()).
可以看到衬潦,connected
的定義是兩個(gè)點(diǎn)組成的集合屬于所有直線的集合的任一個(gè)的子集斤蔓。而in_same_line
則是三個(gè)點(diǎn)的集合屬于所有直線的集合的任一個(gè)的子集。
我們在這里將該問題映射到熟悉的集合領(lǐng)域镀岛。
在有了對connected
和in_same_line
的定以后弦牡,我們就可以對triangle
進(jìn)行測試了!
test() ->
true = triangle("abc"),
false = triangle("abh").
下來我們來進(jìn)行數(shù)三角形漂羊。要能夠數(shù)三角形驾锰,我們需要找到所有三個(gè)點(diǎn)的組合,用來驗(yàn)證是否滿足triangle
走越?將滿足triangle
的進(jìn)行統(tǒng)計(jì)椭豫,這樣我們就得到了結(jié)果!
在這里我們已經(jīng)有了所有點(diǎn)的集合:
Points = "abcdefghijk"
為了得到所有3個(gè)點(diǎn)的組合旨指,我們實(shí)現(xiàn)一個(gè)算法赏酥,對于集合L,求其N個(gè)元素的所有組合的集合谆构。
comb(L, 1) -> [[I] || I <- L];
comb(L, N) when length(L) =:= N -> [L];
comb([H|T], N) ->
[[H|R] || R <- comb(T, N - 1)] ++ comb(T, N).
Points = "abcdefghijk",
TriplePoints = comb(Points, 3),
下面我們實(shí)現(xiàn)一個(gè)count
方法,用來數(shù)滿足要求的三角形個(gè)數(shù):
count(Triple) -> count(Triple, 0).
count([], N) -> N;
count([H|T], N) ->
case triangle(H) of
true -> count(T, N + 1);
false -> count(T, N)
end.
最后可以調(diào)用run
測試一下是不是24搬素!
run() ->
Points = "abcdefghijk",
TriplePoints = comb(Points, 3),
count(TriplePoints).