aboutsummaryrefslogtreecommitdiffstats
path: root/benchmarks/src/jmh/kotlin/benchmarks/flow/scrabble/SequencePlaysScrabble.kt
blob: 5f4f4c2d1ac61122fc42340617ed0098a0377e21 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
/*
 * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
 */

package benchmarks.flow.scrabble

import kotlinx.coroutines.*
import kotlinx.coroutines.flow.*
import org.openjdk.jmh.annotations.*
import java.lang.Long.*
import java.util.*
import java.util.concurrent.*

@Warmup(iterations = 7, time = 1, timeUnit = TimeUnit.SECONDS)
@Measurement(iterations = 7, time = 1, timeUnit = TimeUnit.SECONDS)
@Fork(value = 1)
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.MILLISECONDS)
@State(Scope.Benchmark)
open class SequencePlaysScrabble : ShakespearePlaysScrabble() {

    @Benchmark
    public override fun play(): List<Map.Entry<Int, List<String>>> {
        val score2: (String) -> Int = { word: String ->
            buildHistogram(word)
                .map { it.letterScore() }
                .sum()
        }

        val bonusForDoubleLetter: (String) -> Int = { word: String ->
            toBeMaxed(word)
                .map { letterScores[it - 'a'.toInt()] }
                .max()!!
        }

        val score3: (String) -> Int = { word: String ->
            val sum = score2(word) + bonusForDoubleLetter(word)
            sum * 2 + if (word.length == 7) 50 else 0
        }

        val buildHistoOnScore: (((String) -> Int) -> Flow<TreeMap<Int, List<String>>>) = { score ->
            flow {
                emit(shakespeareWords.asSequence()
                    .filter({ scrabbleWords.contains(it) && checkBlanks(it) })
                    .fold(TreeMap<Int, List<String>>(Collections.reverseOrder())) { acc, value ->
                        val key = score(value)
                        var list = acc[key] as MutableList<String>?
                        if (list == null) {
                            list = ArrayList()
                            acc[key] = list
                        }
                        list.add(value)
                        acc
                    })
            }
        }

        return runBlocking {
            buildHistoOnScore(score3)
                .flatMapConcatIterable { it.entries }
                .take(3)
                .toList()
        }
    }

    private fun Map.Entry<Int, MutableLong>.letterScore(): Int = letterScores[key - 'a'.toInt()] * Integer.min(
        value.get().toInt(),
        scrabbleAvailableLetters[key - 'a'.toInt()])

    private fun toBeMaxed(word: String) = word.asSequence(startIndex = 3) + word.asSequence(endIndex = 3)

    private fun checkBlanks(word: String) = numBlanks(word) <= 2L

    private fun numBlanks(word: String): Long {
        return buildHistogram(word)
            .map { blanks(it) }
            .sum()
    }

    private fun blanks(entry: Map.Entry<Int, MutableLong>): Long =
        max(0L, entry.value.get() - scrabbleAvailableLetters[entry.key - 'a'.toInt()])

    private fun buildHistogram(word: String): HashMap<Int, MutableLong> {
        return word.asSequence().fold(HashMap()) { accumulator, value ->
            var newValue: MutableLong? = accumulator[value]
            if (newValue == null) {
                newValue = MutableLong()
                accumulator[value] = newValue
            }
            newValue.incAndSet()
            accumulator
        }
    }

    private fun String.asSequence(startIndex: Int = 0, endIndex: Int = length) = object : Sequence<Int> {
        override fun iterator(): Iterator<Int> = object : Iterator<Int> {
            private val _endIndex = endIndex.coerceAtMost(length)
            private var currentIndex = startIndex
            override fun hasNext(): Boolean = currentIndex < _endIndex
            override fun next(): Int = get(currentIndex++).toInt()
        }
    }
}