3

我编写了这个 F# 代码来计算列表中的词频并将一个元组返回给 C#。您能告诉我如何使代码更高效或更短吗?

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
4

3 回答 3

16

甚至 pad 的版本也可以变得更加高效和简洁:

let countWords = Seq.countBy id

例子:

countWords ["a"; "a"; "b"; "c"] //returns: seq [("a", 2); ("b", 1); ("c", 1)]
于 2012-03-07T21:37:59.483 回答
7

如果你想计算字符串列表中的词频,你的方法似乎有点过头了。Seq.groupBy非常适合此目的:

let public countWords (words: string list) = 
   words |> Seq.groupBy id
         |> Seq.map (fun (word, sq) -> word, Seq.length sq)
         |> Seq.toList
于 2012-03-07T21:18:07.833 回答
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# 函数的简单解决方案,您可以Seq.groupBy按照 pad 的建议使用。这可能几乎与Dictionary基础版本一样有效。但是,如果您只是在学习 F#,那么像您countWords自己一样编写一些递归函数是一种很好的学习方式!

给你一些关于你的代码的评论 - 你的方法的复杂性略高,但这应该没问题。但是有一些常见的问题:

  • 在你的countword2函数中,你有if h = wrd then ... else last @ [h], count. 该调用last @ [h]效率低下,因为它需要克隆整个 list last。取而代之的是,您可以只写h::last将单词添加到开头,因为顺序无关紧要。

  • 在最后一行,您@再次使用 in [(wrd, count)] @ countword temp。这不是必需的。如果要将单个元素添加到列表的开头,则应使用:(wrd,count)::(countword temp)

于 2012-03-07T21:18:49.900 回答