blob: 2d900cad879920da861d9513de299b0f7a109ff5 [file] [log] [blame]
/*
* Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
package benchmarks.flow.scrabble;
import java.util.*;
import java.util.Map.Entry;
import java.util.concurrent.TimeUnit;
import hu.akarnokd.rxjava2.math.MathFlowable;
import org.openjdk.jmh.annotations.*;
import benchmarks.flow.scrabble.optimizations.*;
import io.reactivex.*;
import io.reactivex.functions.Function;
/**
* Shakespeare plays Scrabble with RxJava 2 Flowable optimized.
* @author José
* @author akarnokd
*/
@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)
public class RxJava2PlaysScrabbleOpt extends ShakespearePlaysScrabble {
static Flowable<Integer> chars(String word) {
// return Flowable.range(0, word.length()).map(i -> (int)word.charAt(i));
return StringFlowable.characters(word);
}
@Benchmark
@Override
public List<Entry<Integer, List<String>>> play() throws Exception {
// to compute the score of a given word
Function<Integer, Integer> scoreOfALetter = letter -> letterScores[letter - 'a'];
// score of the same letters in a word
Function<Entry<Integer, MutableLong>, Integer> letterScore =
entry ->
letterScores[entry.getKey() - 'a'] *
Integer.min(
(int)entry.getValue().get(),
scrabbleAvailableLetters[entry.getKey() - 'a']
)
;
Function<String, Flowable<Integer>> toIntegerFlowable =
string -> chars(string);
Map<String, Single<HashMap<Integer, MutableLong>>> histoCache = new HashMap<>();
// Histogram of the letters in a given word
Function<String, Single<HashMap<Integer, MutableLong>>> histoOfLetters =
word -> { Single<HashMap<Integer, MutableLong>> s = histoCache.get(word);
if (s == null) {
s = toIntegerFlowable.apply(word)
.collect(
() -> new HashMap<>(),
(HashMap<Integer, MutableLong> map, Integer value) ->
{
MutableLong newValue = map.get(value) ;
if (newValue == null) {
newValue = new MutableLong();
map.put(value, newValue);
}
newValue.incAndSet();
}
);
histoCache.put(word, s);
}
return s;
};
// number of blanks for a given letter
Function<Entry<Integer, MutableLong>, Long> blank =
entry ->
Long.max(
0L,
entry.getValue().get() -
scrabbleAvailableLetters[entry.getKey() - 'a']
)
;
// number of blanks for a given word
Function<String, Flowable<Long>> nBlanks =
word -> MathFlowable.sumLong(
histoOfLetters.apply(word).flattenAsFlowable(
map -> map.entrySet()
)
.map(blank)
)
;
// can a word be written with 2 blanks?
Function<String, Flowable<Boolean>> checkBlanks =
word -> nBlanks.apply(word)
.map(l -> l <= 2L) ;
// score taking blanks into account letterScore1
Function<String, Flowable<Integer>> score2 =
word -> MathFlowable.sumInt(
histoOfLetters.apply(word).flattenAsFlowable(
map -> map.entrySet()
)
.map(letterScore)
) ;
// Placing the word on the board
// Building the streams of first and last letters
Function<String, Flowable<Integer>> first3 =
word -> chars(word).take(3) ;
Function<String, Flowable<Integer>> last3 =
word -> chars(word).skip(3) ;
// Stream to be maxed
Function<String, Flowable<Integer>> toBeMaxed =
word -> Flowable.concat(first3.apply(word), last3.apply(word))
;
// Bonus for double letter
Function<String, Flowable<Integer>> bonusForDoubleLetter =
word -> MathFlowable.max(toBeMaxed.apply(word)
.map(scoreOfALetter)
) ;
// score of the word put on the board
Function<String, Flowable<Integer>> score3 =
word ->
MathFlowable.sumInt(Flowable.concat(
score2.apply(word),
bonusForDoubleLetter.apply(word)
)).map(v -> v * 2 + (word.length() == 7 ? 50 : 0));
Function<Function<String, Flowable<Integer>>, Single<TreeMap<Integer, List<String>>>> buildHistoOnScore =
score -> Flowable.fromIterable(shakespeareWords)
.filter(scrabbleWords::contains)
.filter(word -> checkBlanks.apply(word).blockingFirst())
.collect(
() -> new TreeMap<Integer, List<String>>(Comparator.reverseOrder()),
(TreeMap<Integer, List<String>> map, String word) -> {
Integer key = score.apply(word).blockingFirst() ;
List<String> list = map.get(key) ;
if (list == null) {
list = new ArrayList<>() ;
map.put(key, list) ;
}
list.add(word) ;
}
) ;
// best key / value pairs
List<Entry<Integer, List<String>>> finalList2 =
buildHistoOnScore.apply(score3).flattenAsFlowable(
map -> map.entrySet()
)
.take(3)
.collect(
() -> new ArrayList<Entry<Integer, List<String>>>(),
(list, entry) -> {
list.add(entry) ;
}
)
.blockingGet();
return finalList2 ;
}
}