使用函数式编程来计数每个标签的出现次数
我对函数式编程和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
所有操作都没有运行。
虽然已经接受的答案毫无疑问地解决了你的问题,但是我感觉这里有一些“有锤子的东西看起来像钉子”。
答案的本质是flatMap
, groupingBy
和eachCount
是解决问题所需的方法,但是在这里使用序列看起来完全没有必要。
以下是正常运行的代码:
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> }