2012-03-07 38 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