Haskell 為什麼列表操作 很昂貴?

2021-09-28 07:28:50 字數 2309 閱讀 3178

博主是haskell新手。學習haskll的時候遇到了一些問題,在尋求答案的過程中產生了一些思考,可能理解存在偏差,希望各位不吝賜教。

《learn you a haskell for great good》裡第六章關於函式foldl(左fold)的部分提到,++操作符比:要昂貴很多,所以我們一般用foldr來構造乙個list

第一次看這段話的時候我並沒有深究(實際上我認為這句話根本就有毛病,因為foldr也得用++,原文作者根本沒把核心問題指出來),因為haskell用都沒用過幾次,自然理解不了內在機制。最近剛好遇上了乙個用(++)的機會,我想把乙個char陣列轉成string陣列,實現如下(不重要,大致就是用foldl將剩餘陣列的內容塞進累加器陣列裡,用++來連線,不想看的直接跳過**吧):

chars2string :: [char] -> [string]

chars2str chars = foldl (\strs c -> strs ++ [[c]]) chars

-- 當時沒想到的更簡便實現

chars2string :: [char] -> [string]

chars2str chars = map (\c -> [c]) chars

突然想到這不正是使用++的最壞情況嗎?所以我花了不少時間去sof等地方看別人的回答,為什麼++會顯得更加昂貴。

++的原始碼實現是遞迴式的,並且底層使用了:,大部分的答案都有提到這一點,去看了原始碼確實是這樣沒錯。(大致的做法就是將++左邊的部分拆成 x1:x2:x3 ... 這樣,右邊不需要遞迴遍歷)

可是仔細想一想這種遞迴的開銷是o(n)。懂一些資料結構知識就知道,往陣列(非鏈式)底部插入乙個(或x個)值,相當於把已經存在的n個值全部抬高乙個(或x個)陣列位,開銷必然是o(n)。也就是說不論你是什麼語言,想要用非鏈式陣列資料結構實現這樣的功能開銷都應該是一樣的。

證明開銷昂貴

先不論別的語言如何,現在博主來證明++開銷的巨大,假如有這樣的情況:

-- 從sof的這個回答裡受到很大啟發:  

let a = ( ( ( [1,2] ) ++ [3] ) ++ [4] )

計算步驟(偽**):

[1,2] ++ [3] => 1 : 2 : [3] => [1,2,3],這裡通過遞迴遍歷將[1,2]拆開,大致消耗o(2)

[1,2,3] ++ [4] => 1 : 2 : 3 : [4] => [1,2,3,4],又通過遞迴遍歷將[1,2,3]拆開,大致消耗o(3)

... 很明顯在步驟2,重複了遞迴遍歷拆開[1,2]這個操作,也就是說繼續這樣迴圈下去,時間複雜度大致為o(1+2+3+4+...+n),似乎可以化簡為o(k * n^2)(k代表++左邊陣列的長度,n代表重複++的次數)

再觀察剛才的**,可以看到這和函式foldl所做的事情差不多,這就是為什麼++開銷在foldl會很昂貴的原因

也可以很廉價

但是,++也可以很廉價,想象這樣的情況:

let a = (   [1]  ++  (   [2]   ++  [3,4]  )  )
相當於1 : 2 : [3,4],時間複雜度是o(n),n是++的次數,這和:操作是一樣的。

上面的**剛好是foldr所做的事情。這就是《learn you a haskell for great good》作者寫下那段話的原因。

對比其他語言

現在回頭反觀其他語言。假設對乙個非鏈式陣列進行如下操作:

nolinkedarray = 

nolinkedarray.prepend(1).prepend(2).prepend(3)

排除該語言對陣列操作優化的可能,難道時間複雜度不也是o(0+1+2+3+...+n)嗎?

我由此思考得出結論,這是乙個普遍存在的問題,和haskell的底層機制沒多大關係。

可是一碼歸一碼,現在陣列的首選應該都是linkedarray吧,鏈式陣列無論往頭部還是尾部插入元素,單次時間複雜度都是o(1),多次操作時間複雜度則是o(n),不會出現o(k * n^2)這種天文運算

Haskell 為什麼列表操作 很昂貴?

博主是haskell新手。學習haskll的時候遇到了一些問題,在尋求答案的過程中產生了一些思考,可能理解存在偏差,希望各位不吝賜教。learn you a haskell for great good 裡第六章關於函式foldl 左fold 的部分提到,操作符比 要昂貴很多,所以我們一般用fold...

為什麼需要列表

變數可以儲存乙個元素,而列表是乙個大容器可以儲存n多個元素,程式可以方便地對這些資料進行整體操作 列表相當於其他語言中的陣列 注意這邊列表強大的地方在於,其可以儲存不同型別的物件,這是容器,是廣義陣列,更加符合我們人的日常思維,變數組的有序結合 變數儲存的是乙個變數的引用 而列表儲存的是多個變數的引...

zz Intel IPP 為什麼很便宜,呵呵

ipp intel integrated performance primitives,英特爾 r 整合效能原件 一直有所聽聞,這次opencv研討會上,ipp的chief architect李信弘 shinn horng lee 也來參加。跟他了解了一下ipp 1.intel不靠ipp賺錢 199...