使用函数式编程来计数每个标签的出现次数

我对函数式编程和Kotlin相当陌生,我一直试图做一个函数,返回一个Map<String, Int>其中的键是一个特定的标签,值是出现次数。 我发现一些文章指出我正确的方向(我认为),但是我不能将代码“翻译”成在Kotlin中可用的东西。

这里有一些代码,以更好地说明我正在做什么,我想完成什么。

我需要从中提取信息的对象(简化):

 class Note { List<String> tags } 

到目前为止的功能:

 private fun extractTags(notes: List<Note>): Map<String, Int> { return notes.map { note -> note.tags } .groupBy { it } .mapValues { it.value.count() } } 

现在编译器给我一个Map<(Mutable)Set<String!>!, Int>的返回类型不匹配,我不确定我得到了期望的结果(因为我仍然无法正确测试)。 我期待的结果是:

 tag1, 1 tag2, 4 tag3, 14 etc. 

你可以在Kotlin中使用Iterable#asSequence ,就像Java-8 stream-api一样。 然后使用Sequence#flatMap将所有tag合并到一个Sequence ,然后使用Sequence#groupingBy来计数每个标签,例如:

 private fun extractTags(notes: List<Note>): Map<String, Int> { return notes.asSequence() .flatMap { it.tags.asSequence() } .groupingBy { it }.eachCount() } 

注意 : Sequence#flatMap和Sequence#groupingBy都是中间操作 ,这意味着如果终端操作 Grouping#eachCount没有被调用。 Sequence所有操作都没有运行。

虽然已经接受的答案毫无疑问地解决了你的问题,但是我感觉这里有一些“有锤子的东西看起来像钉子”。

答案的本质是flatMapgroupingByeachCount是解决问题所需的方法,但是在这里使用序列看起来完全没有必要。

以下是正常运行的代码:

 private fun extractTags(notes: List<Note>): Map<String, Int> { return notes.flatMap { it.tags } .groupingBy { it } .eachCount() } 

我想争辩说,这是比使用序列更好的解决方案,因为:

  • 它产生相同的结果,因为它使用相同的操作符。
  • 代码只是简单,更容易阅读没有他们。
  • 这里的转换很简单,很少,当你有很长的链时序列会变得有用。
  • 我们可能在这里运行相对较小的数据集。 在我自己的快速测量中,使用序列的解决方案在有一百万个音符的时候快了大约10%,但是只有一万个音符的时候慢了17%。 我敢打赌你的列表大小更接近后者。 序列有开销。
  • 我们并没有充分利用序列提供的懒惰,因为我们要立即评估并返回结果。

你可以在这里看到两种方式的优点和缺点,以及更多的细节。

这里是你的代码修改工作。 我将map更改为flatMap 。 我还提供了一个作为扩展功能实现的版本。 你的失败是因为map>正在生成一个List<List<String>>地方,你希望List<String> (因此是flagMap )。

 fun extractTags(notes: List<Note>): Map<String, Int> { return notes.flatMap { it.tags } // results in List<String> .groupBy { it } // results in Pair<String, List<String>> .mapValues { it.value.count() } } fun Iterable<Note>.extractTags(): Map<String, Int> { return this.flatMap { it.tags } // results in List<String> .groupBy { it } // results in Pair<String, List<String>> .mapValues { it.value.count() } } 

这里有一些代码来测试它

 import kotlin.collections.* fun main(vararg args: String) : Unit { var notes = ArrayList<Note>() notes.add(Note(List<String>(1) { "tag1" })) notes.add(Note(List<String>(4) { "tag4" })) notes.add(Note(List<String>(14) { "tag14" })) for((first,second) in extractTags(notes)) println("$first: $second") for((first,second) in notes.extractTags()) println("$first: $second") } class Note { constructor(strings: List<String>) { tags = strings } var tags: List<String> }