anchor.diff:  
   
   [collectorsMinMax]
  -==== Minimums and maximums
  +===== Minimums and maximums
   
   To extract the minimum or maximum of a group, use the `ConstraintCollectors.min(...)` and
   `ConstraintCollectors.max(...)` collectors respectively.
  
positive.code: ----
  
  
  [[constraintStreamsIntroduction]]
  == Introduction
  
  Using Java 8's Streams API, we could implement an <<easyJavaScoreCalculation,easy score calculator>>
  that uses a functional approach:
  
  [source,java,options="nowrap"]
  ----
      private int speakerUnavailableTimeslot() {
          int softScore = 0;
          schedule.getTalkList().stream()
                  .filter(Talk::hasAnyUnavailableSpeaker)
                  .forEach(talk -> {
                      softScore -= talk.getDurationInMinutes();
                  });
          return softScore;
      }
  ----
  
  However, that scales poorly because it doesn't do an <<incrementalScoreCalculation,incremental calculation>>:
  When the planning variable of a single `Talk` changes, to recalculate the score,
  the normal Streams API has to execute the entire stream from scratch.
  
  On the other hand, the ConstraintStreams API uses <<incrementalScoreCalculation,deltas>>:
  
  ////
  TODO Replace the previous sentence when PLANNER-1709 is fixed.
  
  So it doesn't do <<incrementalScoreCalculation,deltas>>.
  Nor does it support <<explainingTheScore,justifications>>.
  
  On the other hand, the ConstraintStreams API uses <<incrementalScoreCalculation,deltas>>
  and supports <<explainingTheScore,justifications>>:
  ////
  
  [source,java,options="nowrap"]
  ----
      private Constraint speakerUnavailableTimeslot(ConstraintFactory factory) {
          return factory.from(Talk.class)
                  .filter(Talk::hasAnyUnavailableSpeaker)
                  .penalize("Speaker unavailable timeslot",
                          HardSoftScore.ONE_SOFT,
                          Talk::getDurationInMinutes);
      }
  ----
  
  image::ConstraintStreams/constraintStreamIntroduction.png[align="center"]
  image::ConstraintStreams/constraintStreamIncrementalCalculation.png[align="center"]
  image::ConstraintStreams/constraintStreamJustification.png[align="center"]
  
  
  [[constraintStreamsFilter]]
  == Filter
  
  image::ConstraintStreams/constraintStreamFilter.png[align="center"]
  
  
  [[constraintStreamsJoin]]
  == Join
  
  image::ConstraintStreams/constraintStreamJoinWithoutJoiners.png[align="center"]
  
  image::ConstraintStreams/constraintStreamJoinWithJoiners.png[align="center"]
  
  
  [[constraintStreamsGroupingAndCollectors]]
  == Grouping and Collectors
  
  Grouping collects items in a stream according to user-provider criteria (also called "group key"), similar to what a
  `GROUP BY` SQL clause does. Additionally, some grouping operations also accept one or more `Collector`s, which provide
  various aggregation functions.
  
  For example, the following code snippet first groups all processes by the computer they run on, sums up all the power
  required by the processes on that computer using the `ConstraintCollectors.sum(...)` collector, and finally penalizes
  
negative.code: `org.optaplanner.core.api.score.stream.ConstraintCollectors` class.
  
  [collectorsCount]
  ===== Collecting `count()`
  
  The `ConstraintCollectors.count(...)` counts all elements in a group. For example, the following use of the collector
  gives a number of items for two separate groups - one where the talks have unavailable speakers, and one where they
  