blob: 6d9169d3d61b06e17a3019b46f8202231135ee2b [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 benchmarks.flow.scrabble.IterableSpliterator;
import benchmarks.flow.scrabble.ShakespearePlaysScrabble;
import io.reactivex.Flowable;
import io.reactivex.Maybe;
import io.reactivex.Single;
import io.reactivex.functions.Function;
import org.openjdk.jmh.annotations.*;
import java.util.*;
import java.util.Map.Entry;
import java.util.concurrent.TimeUnit;
/**
* Shakespeare plays Scrabble with RxJava 2 Flowable.
* @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 RxJava2PlaysScrabble extends ShakespearePlaysScrabble {
@Benchmark
@Override
public List<Entry<Integer, List<String>>> play() throws Exception {
// Function to compute the score of a given word
Function<Integer, Flowable<Integer>> scoreOfALetter = letter -> Flowable.just(letterScores[letter - 'a']) ;
// score of the same letters in a word
Function<Entry<Integer, LongWrapper>, Flowable<Integer>> letterScore =
entry ->
Flowable.just(
letterScores[entry.getKey() - 'a'] *
Integer.min(
(int)entry.getValue().get(),
scrabbleAvailableLetters[entry.getKey() - 'a']
)
) ;
Function<String, Flowable<Integer>> toIntegerFlowable =
string -> Flowable.fromIterable(IterableSpliterator.of(string.chars().boxed().spliterator())) ;
// Histogram of the letters in a given word
Function<String, Single<HashMap<Integer, LongWrapper>>> histoOfLetters =
word -> toIntegerFlowable.apply(word)
.collect(
() -> new HashMap<>(),
(HashMap<Integer, LongWrapper> map, Integer value) ->
{
LongWrapper newValue = map.get(value) ;
if (newValue == null) {
newValue = () -> 0L ;
}
map.put(value, newValue.incAndSet()) ;
}
) ;
// number of blanks for a given letter
Function<Entry<Integer, LongWrapper>, Flowable<Long>> blank =
entry ->
Flowable.just(
Long.max(
0L,
entry.getValue().get() -
scrabbleAvailableLetters[entry.getKey() - 'a']
)
) ;
// number of blanks for a given word
Function<String, Maybe<Long>> nBlanks =
word -> histoOfLetters.apply(word)
.flatMapPublisher(map -> Flowable.fromIterable(() -> map.entrySet().iterator()))
.flatMap(blank)
.reduce(Long::sum) ;
// can a word be written with 2 blanks?
Function<String, Maybe<Boolean>> checkBlanks =
word -> nBlanks.apply(word)
.flatMap(l -> Maybe.just(l <= 2L)) ;
// score taking blanks into account letterScore1
Function<String, Maybe<Integer>> score2 =
word -> histoOfLetters.apply(word)
.flatMapPublisher(map -> Flowable.fromIterable(() -> map.entrySet().iterator()))
.flatMap(letterScore)
.reduce(Integer::sum) ;
// Placing the word on the board
// Building the streams of first and last letters
Function<String, Flowable<Integer>> first3 =
word -> Flowable.fromIterable(IterableSpliterator.of(word.chars().boxed().limit(3).spliterator())) ;
Function<String, Flowable<Integer>> last3 =
word -> Flowable.fromIterable(IterableSpliterator.of(word.chars().boxed().skip(3).spliterator())) ;
// Stream to be maxed
Function<String, Flowable<Integer>> toBeMaxed =
word -> Flowable.just(first3.apply(word), last3.apply(word))
.flatMap(observable -> observable) ;
// Bonus for double letter
Function<String, Maybe<Integer>> bonusForDoubleLetter =
word -> toBeMaxed.apply(word)
.flatMap(scoreOfALetter)
.reduce(Integer::max) ;
// score of the word put on the board
Function<String, Maybe<Integer>> score3 =
word ->
Maybe.merge(Arrays.asList(
score2.apply(word),
score2.apply(word),
bonusForDoubleLetter.apply(word),
bonusForDoubleLetter.apply(word),
Maybe.just(word.length() == 7 ? 50 : 0)
)
)
.reduce(Integer::sum) ;
Function<Function<String, Maybe<Integer>>, Single<TreeMap<Integer, List<String>>>> buildHistoOnScore =
score -> Flowable.fromIterable(() -> shakespeareWords.iterator())
.filter(scrabbleWords::contains)
.filter(word -> checkBlanks.apply(word).blockingGet())
.collect(
() -> new TreeMap<>(Comparator.reverseOrder()),
(TreeMap<Integer, List<String>> map, String word) -> {
Integer key = score.apply(word).blockingGet() ;
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)
.flatMapPublisher(map -> Flowable.fromIterable(() -> map.entrySet().iterator()))
.take(3)
.collect(
() -> new ArrayList<Entry<Integer, List<String>>>(),
(list, entry) -> {
list.add(entry) ;
}
)
.blockingGet() ;
return finalList2 ;
}
}