content: /*
   * Copyright 2015, 2024 StreamEx contributors
   *
   * Licensed under the Apache License, Version 2.0 (the "License");
   * you may not use this file except in compliance with the License.
   * You may obtain a copy of the License at
   *
   *     http://www.apache.org/licenses/LICENSE-2.0
   *
   * Unless required by applicable law or agreed to in writing, software
   * distributed under the License is distributed on an "AS IS" BASIS,
   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   * See the License for the specific language governing permissions and
   * limitations under the License.
   */
  package one.util.streamex;
  
  import org.junit.Test;
  
  import java.math.BigDecimal;
  import java.math.BigInteger;
  import java.math.MathContext;
  import java.util.Arrays;
  import java.util.OptionalDouble;
  import java.util.function.BiFunction;
  import java.util.function.Supplier;
  import java.util.stream.Collector;
  import java.util.stream.Collectors;
  import java.util.stream.IntStream;
  import java.util.stream.LongStream;
  
  import static one.util.streamex.Internals.AverageLong;
  import static one.util.streamex.TestHelpers.repeat;
  import static one.util.streamex.TestHelpers.withRandom;
  import static org.junit.Assert.assertEquals;
  import static org.junit.Assert.assertFalse;
  
  /**
   * @author Tagir Valeev
   */
  public class AverageLongTest {
  
      @Test
      public void testAverageLongNoOverflow() {
          AverageLong avg = new AverageLong();
          assertFalse(avg.result().isPresent());
          avg.accept(1);
          avg.accept(2);
          avg.accept(3);
          assertEquals(2.0, avg.result().getAsDouble(), 0.0);
  
          avg.accept(2);
          avg.accept(-4);
          avg.accept(8);
          assertEquals(2.0, avg.result().getAsDouble(), 0.0);
  
          AverageLong avg1 = new AverageLong();
          avg1.accept(-2);
          AverageLong avg2 = new AverageLong();
          avg2.accept(-2);
          assertEquals(-2.0, avg1.combine(avg2).result().getAsDouble(), 0.0);
  
          withRandom(r -> {
              int[] input = r.ints(1000).toArray();
              OptionalDouble expected = IntStream.of(input).average();
              assertEquals(expected, Arrays.stream(input)
                      .collect(AverageLong::new, AverageLong::accept, AverageLong::combine).result());
  
              assertEquals(expected, Arrays.stream(input).parallel().collect(AverageLong::new, AverageLong::accept,
                  AverageLong::combine).result());
          });
      }
  
      @Test
      public void testCombine() {
          withRandom(r -> repeat(100, i -> {
              AverageLong avg1 = new AverageLong();
              AverageLong avg2 = new AverageLong();
              long[] set1 = r.longs(100).toArray();
              long[] set2 = r.longs(100).toArray();
              double expected = LongStreamEx.of(set1).append(set2).boxed().collect(getBigIntegerAverager()).getAsDouble();
              LongStream.of(set1).forEach(avg1::accept);
              LongStream.of(set2).forEach(avg2::accept);
              assertEquals(expected, avg1.combine(avg2).result().getAsDouble(), Math.abs(expected / 1e14));
          }));
      }
  
      @Test
      public void testCompareToBigInteger() {
          withRandom(r -> {
              long[] input = LongStreamEx.of(r, 1000).toArray();
              Supplier<LongStream> supplier = () -> Arrays.stream(input);
              double expected = supplier.get().boxed().collect(getBigIntegerAverager()).getAsDouble();
              assertEquals(expected, supplier.get().collect(AverageLong::new, AverageLong::accept, AverageLong::combine)
                      .result().getAsDouble(), Math.abs(expected) / 1e14);
              assertEquals(expected, supplier.get().parallel().collect(AverageLong::new, AverageLong::accept,
                  AverageLong::combine).result().getAsDouble(), Math.abs(expected) / 1e14);
          });
      }
  
      private static Collector<Long, ?, OptionalDouble> getBigIntegerAverager() {
          BiFunction<BigInteger, Long, OptionalDouble> finisher = (BigInteger sum, Long cnt) -> cnt == 0L ? OptionalDouble
                  .empty()
                  : OptionalDouble.of(new BigDecimal(sum).divide(BigDecimal.valueOf(cnt), MathContext.DECIMAL64)
                          .doubleValue());
          return MoreCollectors.pairing(Collectors.reducing(BigInteger.ZERO,
              BigInteger::valueOf, BigInteger::add), Collectors.counting(), finisher);
      }
  }
  