使用函数式编程计算每个标记的出现次数

时间:2017-07-24 14:10:23

标签: functional-programming kotlin

我一直试图创建一个返回Map<String, Int>的函数,其中键是某个标记,值是出现次数。

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

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)
...

4 个答案:

答案 0 :(得分:8)

您可以像在Kotlin中使用Java-8 stream-api一样使用Iterable#asSequence。然后使用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#flatMapSequence#groupingBy都是intermediate operations,这意味着如果未调用terminal operation Grouping#eachCountSequence上的所有操作都没有运行。

答案 1 :(得分:3)

虽然已经接受的答案无可争议地解决了你的问题,但我觉得这里有一点“当你有锤子时,一切看起来像钉子”。

答案的实质是flatMapgroupingByeachCount是解决问题所需的方法,但是,在这里使用序列似乎完全没必要。

以下是对常规集合进行操作的代码:

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

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

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

您可以看到两种方式与利弊here的完美比较以及更多细节。

答案 2 :(得分:0)

以下是您修改后的代码。我将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>
}

答案 3 :(得分:0)

不好意思,但是这是最好的解决方案:我认为,当您使用Kotlin时,您拥有的标准库比Java 8流提供了更好的语法,更短,更干净。

private fun extractTags(notes: List<Note>): Map<String, Int> = notes.flatMap { it.tags }//list of String
        .groupBy { it }//list of Map.Entry<String,List<String>> //List<Map.Entry<String,List<String>>>
        .map {
            Pair(it.key, it.value.size)
        }//list of pairs(tag, count) // List<Pair(String,Int) 
       .toMap()//creat a map from the list of pairs