| /* |
| * Copyright 2016-2020 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 ; |
| } |
| } |