2014-05-19 91 views
4

我想在Haskell中硬編碼地圖。我至少可以看到3路做在haskell中硬編碼地圖的最有效方法是什麼

使用多個定義(我不rememeber確切的名稱)

message 200 = "OK" 
message 404 = "Not found" 
... 

使用

message s = case s of 
    200 -> "OK" 
    404 -> "Not found" 

的情況下使用地圖?

etc ...

這是最有效的方法嗎?一種解決方案比其他解決方案更快,爲什麼? 前兩種解決方案是否相同? (編譯器會生成相同的代碼嗎?) 推薦的方法是什麼(更易於閱讀)?

==更新== 我在我的例子中使用了Int,但這只是一個例子。那也可能是String,所以我對這兩種情況都很感興趣。

+0

如果您對效率感興趣,'String'在大多數情況下不適合使用。 GHC開發人員甚至不太可能試圖爲模式匹配「字符串」編寫特殊優化 - 我想他們的匹配方式與其他列表相同。 – dfeuer

+0

對於字符串,您可能需要考慮使用'bytestring'和'bytestring-trie'軟件包。 – dfeuer

回答

1

case ... of和多個方程式完全相同。他們編譯到相同的核心。對於大量的情況,你應該這樣做:

import qualified Data.Map as Map 

message = 
    let 
     theMap = Map.fromList [ (200, "OK"), (404, "Not found"), ... ] 
    in 
     \x -> Map.lookup x theMap 

這隻構造一次地圖。如果您不喜歡Maybe String返回類型,則可以將fromMaybe應用於結果。

對於少數情況(特別是如果它們是整數),如果編譯器可以將它轉換爲跳轉表,那麼case語句可能會更快。

在理想的世界裏,ghc會自動選擇正確的版本。

+0

跳轉表對於像Bool這樣的小型類型(很好,也許不是那麼小)很有意義,在某些情況下對於像Word8這樣的中型類型也可能會有意義。作爲較大類型的混合方案的一部分(即,首先檢查範圍,然後如果在範圍內則跳轉),但是對於諸如「Int」的大類型的任意值,可以是線性掃描(對於少數情況)或者我相信,類似地圖的實施(對於大量案例)應該更有效率。 – dfeuer

+1

這個haskell-cafe線程涉及以下主題:https://groups.google.com/d/msg/haskell-cafe/-YTVNbNHHLo/rTWtxwZm8hkJ – ErikR

3

Apparently功能模式匹配在O(1)(恆定時間)發生,而Map的查找(當然是指Data.Map的)保證是O(logn)

考慮到上述假設,我會用模式匹配去:

message 200 = "OK" 
message 404 = "Not found" 
+2

您是否有線性複雜性的證據?如果對於小代數類型,我會感到非常驚訝,但是我認爲它可能適用於'Int'。 – dfeuer

+0

@dfeuer,實際上[它是不變的時間](http://stackoverflow.com/a/9027441/493122)。有趣。相應地更新答案。謝謝你讓我檢查。 – Shoe

+2

你確定匹配一個'Int'的構造函數的方式與爲構造函數匹配標記的方式相同,這是鏈接答案中發生了什麼? – Cirdec

6

Int s碼匹配發生在O(log(n))時間,就像一張地圖查找。

通過ghc -S

module F (
    f 
) where 

f :: Int -> String 
f 0 = "Zero" 
f 1 = "One" 
f 2 = "Two" 
f 3 = "Three" 
f 4 = "Four" 
f 5 = "Five" 
f 6 = "Six" 
f 7 = "Seven" 
f _ = "Undefined" 

考慮下面的代碼,編譯爲x86彙編編譯後的彙編代碼

.text 
    .align 4,0x90 
    .long _F_f_srt-(_sl8_info)+0 
    .long 0 
    .long 65568 
_sl8_info: 
.Lcma: 
    movl 3(%esi),%eax 
    cmpl $4,%eax 
    jl .Lcmq 
    cmpl $6,%eax 
    jl .Lcmi 
    cmpl $7,%eax 
    jl .Lcme 
    cmpl $7,%eax 
    jne .Lcmc 
    movl $_ghczmprim_GHCziCString_unpackCStringzh_closure,%esi 
    movl $_cm7_str,0(%ebp) 
    jmp _stg_ap_n_fast 
.Lcmc: 
    movl $_ghczmprim_GHCziCString_unpackCStringzh_closure,%esi 
    movl $_clB_str,0(%ebp) 
    jmp _stg_ap_n_fast 
.Lcme: 
    cmpl $6,%eax 
    jne .Lcmc 
    movl $_ghczmprim_GHCziCString_unpackCStringzh_closure,%esi 
    movl $_cm3_str,0(%ebp) 
    jmp _stg_ap_n_fast 
.Lcmg: 
    cmpl $4,%eax 
    jne .Lcmc 
    movl $_ghczmprim_GHCziCString_unpackCStringzh_closure,%esi 
    movl $_clV_str,0(%ebp) 
    jmp _stg_ap_n_fast 
.Lcmi: 
    cmpl $5,%eax 
    jl .Lcmg 
    cmpl $5,%eax 
    jne .Lcmc 
    movl $_ghczmprim_GHCziCString_unpackCStringzh_closure,%esi 
    movl $_clZ_str,0(%ebp) 
    jmp _stg_ap_n_fast 
.Lcmk: 
    cmpl $2,%eax 
    jne .Lcmc 
    movl $_ghczmprim_GHCziCString_unpackCStringzh_closure,%esi 
    movl $_clN_str,0(%ebp) 
    jmp _stg_ap_n_fast 
.Lcmm: 
    testl %eax,%eax 
    jne .Lcmc 
    movl $_ghczmprim_GHCziCString_unpackCStringzh_closure,%esi 
    movl $_clF_str,0(%ebp) 
    jmp _stg_ap_n_fast 
.Lcmo: 
    cmpl $1,%eax 
    jl .Lcmm 
    cmpl $1,%eax 
    jne .Lcmc 
    movl $_ghczmprim_GHCziCString_unpackCStringzh_closure,%esi 
    movl $_clJ_str,0(%ebp) 
    jmp _stg_ap_n_fast 
.Lcmq: 
    cmpl $2,%eax 
    jl .Lcmo 
    cmpl $3,%eax 
    jl .Lcmk 
    cmpl $3,%eax 
    jne .Lcmc 
    movl $_ghczmprim_GHCziCString_unpackCStringzh_closure,%esi 
    movl $_clR_str,0(%ebp) 
    jmp _stg_ap_n_fast 
.text 
    .align 4,0x90 
    .long _F_f_srt-(_F_f_info)+0 
    .long 65541 
    .long 0 
    .long 65551 
.globl _F_f_info 
_F_f_info: 
.Lcmu: 
    movl 0(%ebp),%esi 
    movl $_sl8_info,0(%ebp) 
    testl $3,%esi 
    jne .Lcmx 
    jmp *(%esi) 
.Lcmx: 
    jmp _sl8_info 

這是幹什麼的整數參數的二進制搜索。 .Lcma被分支上< 4然後< 6然後< 7.第一comparsion進入.Lcmq,其上< 2然後< 3.支化第一比較從去.Lcmo,其分支上< 1。

隨着ghc -O2 -S,相反,我們得到這個,在這裏我們可以看到相同的模式:

.text 
    .align 4,0x90 
    .long _F_zdwf_srt-(_F_zdwf_info)+0 
    .long 65540 
    .long 0 
    .long 33488911 
.globl _F_zdwf_info 
_F_zdwf_info: 
.LcqO: 
    movl 0(%ebp),%eax 
    cmpl $4,%eax 
    jl .Lcr6 
    cmpl $6,%eax 
    jl .LcqY 
    cmpl $7,%eax 
    jl .LcqU 
    cmpl $7,%eax 
    jne .LcqS 
    movl $_F_f1_closure,%esi 
    addl $4,%ebp 
    andl $-4,%esi 
    jmp *(%esi) 
.LcqS: 
    movl $_F_f9_closure,%esi 
    addl $4,%ebp 
    andl $-4,%esi 
    jmp *(%esi) 
.LcqU: 
    cmpl $6,%eax 
    jne .LcqS 
    movl $_F_f2_closure,%esi 
    addl $4,%ebp 
    andl $-4,%esi 
    jmp *(%esi) 
.LcqW: 
    cmpl $4,%eax 
    jne .LcqS 
    movl $_F_f4_closure,%esi 
    addl $4,%ebp 
    andl $-4,%esi 
    jmp *(%esi) 
.LcqY: 
    cmpl $5,%eax 
    jl .LcqW 
    cmpl $5,%eax 
    jne .LcqS 
    movl $_F_f3_closure,%esi 
    addl $4,%ebp 
    andl $-4,%esi 
    jmp *(%esi) 
.Lcr0: 
    cmpl $2,%eax 
    jne .LcqS 
    movl $_F_f6_closure,%esi 
    addl $4,%ebp 
    andl $-4,%esi 
    jmp *(%esi) 
.Lcr2: 
    testl %eax,%eax 
    jne .LcqS 
    movl $_F_f8_closure,%esi 
    addl $4,%ebp 
    andl $-4,%esi 
    jmp *(%esi) 
.Lcr4: 
    cmpl $1,%eax 
    jl .Lcr2 
    cmpl $1,%eax 
    jne .LcqS 
    movl $_F_f7_closure,%esi 
    addl $4,%ebp 
    andl $-4,%esi 
    jmp *(%esi) 
.Lcr6: 
    cmpl $2,%eax 
    jl .Lcr4 
    cmpl $3,%eax 
    jl .Lcr0 
    cmpl $3,%eax 
    jne .LcqS 
    movl $_F_f5_closure,%esi 
    addl $4,%ebp 
    andl $-4,%esi 
    jmp *(%esi) 
.section .data 
    .align 4 
.align 1 
_F_f_srt: 
    .long _F_zdwf_closure 
.data 
    .align 4 
.align 1 
.globl _F_f_closure 
_F_f_closure: 
    .long _F_f_info 
    .long 0 
.text 
    .align 4,0x90 
    .long _F_f_srt-(_srh_info)+0 
    .long 0 
    .long 65568 
_srh_info: 
.Lcrv: 
    movl 3(%esi),%eax 
    movl %eax,0(%ebp) 
    jmp _F_zdwf_info 
.text 
    .align 4,0x90 
    .long _F_f_srt-(_F_f_info)+0 
    .long 65541 
    .long 0 
    .long 65551 
.globl _F_f_info 
_F_f_info: 
.Lcrz: 
    movl 0(%ebp),%esi 
    movl $_srh_info,0(%ebp) 
    testl $3,%esi 
    jne _srh_info 
    jmp *(%esi) 

如果我們改變原來的代碼

f :: Int -> String 
f 1 = "Zero" 
f 2 = "One" 
f 3 = "Two" 
f 4 = "Three" 
f 5 = "Four" 
f 6 = "Five" 
f 7 = "Six" 
f 8 = "Seven" 
f _ = "Undefined" 

樹枝上< 5,< 7 ,< 8,< 5去< 3,< 4等,所以它可能是基於排序參數做到這一點。我們可以測試通過加密的數字,甚至將它們之間的間距:

f :: Int -> String 
f 20 = "Zero" 
f 80 = "One" 
f 70 = "Two" 
f 30 = "Three" 
f 40 = "Four" 
f 50 = "Five" 
f 10 = "Six" 
f 60 = "Seven" 
f _ = "Undefined" 

果然,樹枝仍在< 50,< 70,< 80,與< 50要< 30,< 40等等。

+0

如果你使用帶'-O2'的ADT,我相信你會得到一個跳轉表。也看到這個haskell咖啡館線程:https://groups.google.com/d/msg/haskell-cafe/-YTVNbNHHLo/rTWtxwZm8hkJ – ErikR

+0

我的問題是更一般的,只是詮釋。查看我的更新 – mb14

相關問題