2012-03-07 39 views
3

我寫了這個F#代碼來計算列表中的單詞頻率並將一個元組返回給C#。你能告訴我如何讓代碼更有效率或更短?如何讓word freq counter更高效?

let rec internal countword2 (tail : string list) wrd ((last : string list), count) = 
match tail with 
| [] -> last, wrd, count 
| h::t -> countword2 t wrd (if h = wrd then last, count+1 else last @ [h], count) 

let internal countword1 (str : string list) wrd = 
let temp, wrd, count = countword2 str wrd ([], 0) in 
temp, wrd, count 

let rec public countword (str : string list) = 
match str with 
| [] -> [] 
| h::_ -> 
    let temp, wrd, count = countword1 str h in 
     [(wrd, count)] @ countword temp 
+2

嘗試http://codereview.stackexchange.com/這類問題。 – 2012-03-08 00:01:10

+0

@MauricioScheffer我不知道codereview.stackexchange.com甚至存在,直到您發佈此評論。有趣。 – 2012-03-10 06:34:10

回答

7

如果你想計算一個字符串列表中的單詞頻率,你的方法似乎是矯枉過正。 Seq.groupBy是良好的配用於此目的:

let public countWords (words: string list) = 
    words |> Seq.groupBy id 
     |> Seq.map (fun (word, sq) -> word, Seq.length sq) 
     |> Seq.toList 
+0

謝謝。這將返回一個元組(字符串,int)到C#? – codious 2012-03-07 21:21:22

+1

是的,功能簽名與您的功能相同。 – pad 2012-03-07 21:24:22

+1

好的答案,但請注意,在這種情況下'Seq.ofList'調用是多餘的。 – kvb 2012-03-07 21:29:16

2

您的解決方案迭代的輸入列表數次,每一個新詞,它創立。而不是這樣做,你可以遍歷列表一次,並建立一個字典,其中包含每個單詞的所有出現次數。

要在實用的風格做到這一點,你可以使用F#Map,這是一個不變的詞典:

let countWords words = 
    // Increment the number of occurrences of 'word' in the map 'counts' 
    // If it isn't already in the dictionary, add it with count 1 
    let increment counts word = 
    match Map.tryFind word counts with 
    | Some count -> Map.add word (count + 1) counts 
    | _ -> Map.add word 1 counts 

    // Start with an empty map and call 'increment' 
    // to add all words to the dictionary 
    words |> List.fold increment Map.empty 

您還可以實現在一個命令行式風格同樣的事情,這將是更有效,但不那麼優雅(並且你沒有得到功能風格的所有好處)。但是,標準可變的Dictionary也可以很好地從F#中使用(這將會類似於C#版本,所以我不會在這裏寫它)。最後,如果你想要一個簡單的解決方案,只使用標準的F#函數,你可以使用pad提示的Seq.groupBy。這可能幾乎和基於Dictionary的版本一樣高效。但是,如果你只是學習F#,那麼自己寫一些遞歸函數如countWords是一種很好的學習方式!

爲了給你一些關於你的代碼的評論 - 你的方法的複雜性稍高,但應該沒問題。然而,有一些常見的isses:

  • 在你countword2功能,你有if h = wrd then ... else last @ [h], count。致電last @ [h]效率低下,因爲它需要克隆整個列表last。而不是這樣,你可以寫h::last來將單詞添加到開頭,因爲順序無關緊要。

  • 在最後一行,您在[(wrd, count)] @ countword temp中再次使用@。這不是必需的。如果您將單個元素添加到列表的開頭,則應該使用:(wrd,count)::(countword temp)

+0

謝謝你的明確解釋。 – codious 2012-03-07 21:26:12

+0

感謝關於代碼的指針。 – codious 2012-03-07 21:43:24

15

即使墊的版本,可以更加高效和簡潔:

let countWords = Seq.countBy id 

例子:

countWords ["a"; "a"; "b"; "c"] //returns: seq [("a", 2); ("b", 1); ("c", 1)] 
+0

得愛一個單行表達式可以做F這麼多的事實# – 2012-03-10 06:36:20

+1

+1最好的答案。準確地說是 – 2012-03-10 15:49:10

+0

。代碼的大小非常棒。 – codious 2012-03-10 20:12:09