我已编写此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
答案 0 :(得分:15)
即使pad的版本也可以更加高效和简洁:
let countWords = Seq.countBy id
示例:
countWords ["a"; "a"; "b"; "c"] //returns: seq [("a", 2); ("b", 1); ("c", 1)]
答案 1 :(得分:7)
如果你想计算字符串列表中的单词频率,你的方法似乎有点矫枉过正。 Seq.groupBy
非常适合此目的:
let public countWords (words: string list) =
words |> Seq.groupBy id
|> Seq.map (fun (word, sq) -> word, Seq.length sq)
|> Seq.toList
答案 2 :(得分: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
一样是一种很好的学习方法!
为了给你一些关于你的代码的评论 - 你的方法的复杂性略高,但这应该没问题。然而,有一些共同的观点:
在countword2
函数中,您有if h = wrd then ... else last @ [h], count
。调用last @ [h]
效率低下,因为它需要克隆整个列表last
。而不是这样,你可以写h::last
来将单词添加到开头,因为顺序并不重要。
在最后一行,您在@
中再次使用[(wrd, count)] @ countword temp
。这不是必需的。如果您要将单个元素添加到列表的开头,则应使用:(wrd,count)::(countword temp)
。