text: /*
   * Copyright 2015 Goldman Sachs.
   *
   * 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 com.gs.collections.impl.parallel;
  
  import java.math.BigDecimal;
  import java.math.BigInteger;
  import java.util.ArrayList;
  import java.util.Arrays;
  import java.util.Collection;
  import java.util.Collections;
  import java.util.LinkedList;
  import java.util.List;
  import java.util.concurrent.ExecutorService;
  import java.util.concurrent.Executors;
  import java.util.concurrent.atomic.AtomicInteger;
  
  import com.gs.collections.api.LazyIterable;
  import com.gs.collections.api.RichIterable;
  import com.gs.collections.api.bag.Bag;
  import com.gs.collections.api.block.function.Function;
  import com.gs.collections.api.block.function.Function2;
  import com.gs.collections.api.block.predicate.Predicate;
  import com.gs.collections.api.block.procedure.Procedure;
  import com.gs.collections.api.block.procedure.Procedure2;
  import com.gs.collections.api.block.procedure.primitive.ObjectIntProcedure;
  import com.gs.collections.api.list.ImmutableList;
  import com.gs.collections.api.list.MutableList;
  import com.gs.collections.api.map.MapIterable;
  import com.gs.collections.api.map.MutableMap;
  import com.gs.collections.api.map.primitive.ObjectDoubleMap;
  import com.gs.collections.api.map.primitive.ObjectLongMap;
  import com.gs.collections.api.multimap.Multimap;
  import com.gs.collections.api.multimap.MutableMultimap;
  import com.gs.collections.api.set.MutableSet;
  import com.gs.collections.impl.bag.mutable.HashBag;
  import com.gs.collections.impl.block.factory.Functions;
  import com.gs.collections.impl.block.factory.HashingStrategies;
  import com.gs.collections.impl.block.factory.Predicates;
  import com.gs.collections.impl.block.factory.Procedures;
  import com.gs.collections.impl.block.factory.StringFunctions;
  import com.gs.collections.impl.factory.Lists;
  import com.gs.collections.impl.list.Interval;
  import com.gs.collections.impl.list.mutable.ArrayListAdapter;
  import com.gs.collections.impl.list.mutable.CompositeFastList;
  import com.gs.collections.impl.list.mutable.FastList;
  import com.gs.collections.impl.list.mutable.ListAdapter;
  import com.gs.collections.impl.map.mutable.UnifiedMap;
  import com.gs.collections.impl.multimap.bag.HashBagMultimap;
  import com.gs.collections.impl.multimap.bag.SynchronizedPutHashBagMultimap;
  import com.gs.collections.impl.multimap.set.SynchronizedPutUnifiedSetMultimap;
  import com.gs.collections.impl.set.mutable.UnifiedSet;
  import com.gs.collections.impl.set.strategy.mutable.UnifiedSetWithHashingStrategy;
  import com.gs.collections.impl.test.Verify;
  import com.gs.collections.impl.utility.ArrayIterate;
  import com.gs.collections.impl.utility.Iterate;
  import com.gs.collections.impl.utility.LazyIterate;
  import org.junit.After;
  import org.junit.Assert;
  import org.junit.Before;
  import org.junit.Test;
  
  public class ParallelIterateTest
  {
      private static final Procedure<Integer> EXCEPTION_PROCEDURE = value -> {
          throw new RuntimeException("Thread death on its way!");
      };
  
      private static final ObjectIntProcedure<Integer> EXCEPTION_OBJECT_INT_PROCEDURE = (object, index) -> {
          throw new RuntimeException("Thread death on its way!");
      };
  
      private static final Function<Integer, Collection<String>> INT_TO_TWO_STRINGS = integer -> Lists.fixedSize.of(integer.toString(), integer.toString());
  
      private static final Function<Integer, String> EVEN_OR_ODD = value -> value % 2 == 0 ? "Even" : "Odd";
      private static final Function<BigDecimal, String> EVEN_OR_ODD_BD = value -> value.intValue() % 2 == 0 ? "Even" : "Odd";
      private static final Function<BigInteger, String> EVEN_OR_ODD_BI = value -> value.intValue() % 2 == 0 ? "Even" : "Odd";
      private static final int UNEVEN_COUNT_FOR_SUMBY = 43957;
  
      private ImmutableList<RichIterable<Integer>> iterables;
      private final ExecutorService executor = Executors.newFixedThreadPool(2);
  
      @Before
      public void setUp()
      {
          Interval interval = Interval.oneTo(200);
          this.iterables = Lists.immutable.of(
                  interval.toList(),
                  interval.toList().asUnmodifiable(),
                  interval.toList().asSynchronized(),
                  interval.toList().toImmutable(),
                  interval.toSet(),
                  interval.toSet().asUnmodifiable(),
                  interval.toSet().asSynchronized(),
                  interval.toSet().toImmutable(),
                  interval.toBag(),
                  interval.toBag().asUnmodifiable(),
                  interval.toBag().asSynchronized(),
                  interval.toBag().toImmutable(),
                  interval.toSortedSet(),
                  interval.toSortedSet().asUnmodifiable(),
                  interval.toSortedSet().asSynchronized(),
                  interval.toSortedSet().toImmutable(),
                  interval.toMap(Functions.<Integer>identity(), Functions.<Integer>identity()),
                  interval.toMap(Functions.<Integer>identity(), Functions.<Integer>identity()).asUnmodifiable(),
                  interval.toMap(Functions.<Integer>identity(), Functions.<Integer>identity()).asSynchronized(),
                  interval.toMap(Functions.<Integer>identity(), Functions.<Integer>identity()).toImmutable(),
                  new CompositeFastList<Integer>().withAll(interval.toList()),
                  new CompositeFastList<Integer>().withAll(interval.toList()).asUnmodifiable(),
                  new CompositeFastList<Integer>().withAll(interval.toList()).asSynchronized(),
                  ArrayListAdapter.<Integer>newList().withAll(interval),
                  ArrayListAdapter.<Integer>newList().withAll(interval).asUnmodifiable(),
                  ArrayListAdapter.<Integer>newList().withAll(interval).asSynchronized(),
                  ListAdapter.adapt(new LinkedList<Integer>()).withAll(interval),
                  ListAdapter.adapt(new LinkedList<Integer>()).withAll(interval).asUnmodifiable(),
                  ListAdapter.adapt(new LinkedList<Integer>()).withAll(interval).asSynchronized(),
                  UnifiedSetWithHashingStrategy.<Integer>newSet(HashingStrategies.defaultStrategy()).withAll(interval),
                  UnifiedSetWithHashingStrategy.<Integer>newSet(HashingStrategies.defaultStrategy()).withAll(interval).asUnmodifiable(),
                  UnifiedSetWithHashingStrategy.<Integer>newSet(HashingStrategies.defaultStrategy()).withAll(interval).asSynchronized(),
                  UnifiedSetWithHashingStrategy.<Integer>newSet(HashingStrategies.defaultStrategy()).withAll(interval).toImmutable());
      }
  
      @After
      public void tearDown()
      {
          this.executor.shutdown();
      }
  
      @Test
      public void testForEachUsingSet()
      {
          //Tests the default batch size calculations
          IntegerSum sum = new IntegerSum(0);
          MutableSet<Integer> set = Interval.toSet(1, 100);
          ParallelIterate.forEach(set, new SumProcedure(sum), new SumCombiner(sum));
          Assert.assertEquals(5050, sum.getSum());
  
          //Testing batch size 1
          IntegerSum sum2 = new IntegerSum(0);
          UnifiedSet<Integer> set2 = UnifiedSet.newSet(Interval.oneTo(100));
          ParallelIterate.forEach(set2, new SumProcedure(sum2), new SumCombiner(sum2), 1, set2.getBatchCount(set2.size()));
          Assert.assertEquals(5050, sum2.getSum());
  
          //Testing an uneven batch size
          IntegerSum sum3 = new IntegerSum(0);
          UnifiedSet<Integer> set3 = UnifiedSet.newSet(Interval.oneTo(100));
          ParallelIterate.forEach(set3, new SumProcedure(sum3), new SumCombiner(sum3), 1, set3.getBatchCount(13));
          Assert.assertEquals(5050, sum3.getSum());
  
          //Testing divideByZero exception by passing 1 as batchSize
          IntegerSum sum4 = new IntegerSum(0);
          UnifiedSet<Integer> set4 = UnifiedSet.newSet(Interval.oneTo(100));
          ParallelIterate.forEach(set4, new SumProcedure(sum4), new SumCombiner(sum4), 1);
          Assert.assertEquals(5050, sum4.getSum());
      }
  
      @Test
      public void testForEachUsingMap()
      {
          //Test the default batch size calculations
          IntegerSum sum1 = new IntegerSum(0);
          MutableMap<String, Integer> map1 = Interval.fromTo(1, 100).toMap(String::valueOf, Functions.getIntegerPassThru());
          ParallelIterate.forEach(map1, new SumProcedure(sum1), new SumCombiner(sum1));
          Assert.assertEquals(5050, sum1.getSum());
  
          //Testing batch size 1
          IntegerSum sum2 = new IntegerSum(0);
          UnifiedMap<String, Integer> map2 = (UnifiedMap<String, Integer>) Interval.fromTo(1, 100).toMap(String::valueOf, Functions.getIntegerPassThru());
          ParallelIterate.forEach(map2, new SumProcedure(sum2), new SumCombiner(sum2), 1, map2.getBatchCount(map2.size()));
          Assert.assertEquals(5050, sum2.getSum());
  
          //Testing an uneven batch size
          IntegerSum sum3 = new IntegerSum(0);
          UnifiedMap<String, Integer> set3 = (UnifiedMap<String, Integer>) Interval.fromTo(1, 100).toMap(String::valueOf, Functions.getIntegerPassThru());
          ParallelIterate.forEach(set3, new SumProcedure(sum3), new SumCombiner(sum3), 1, set3.getBatchCount(13));
          Assert.assertEquals(5050, sum3.getSum());
      }
  
      @Test
      public void testForEach()
      {
          IntegerSum sum1 = new IntegerSum(0);
          List<Integer> list1 = ParallelIterateTest.createIntegerList(16);
          ParallelIterate.forEach(list1, new SumProcedure(sum1), new SumCombiner(sum1), 1, list1.size() / 2);
          Assert.assertEquals(16, sum1.getSum());
  
          IntegerSum sum2 = new IntegerSum(0);
          List<Integer> list2 = ParallelIterateTest.createIntegerList(7);
          ParallelIterate.forEach(list2, new SumProcedure(sum2), new SumCombiner(sum2));
          Assert.assertEquals(7, sum2.getSum());
  
          IntegerSum sum3 = new IntegerSum(0);
          List<Integer> list3 = ParallelIterateTest.createIntegerList(15);
          ParallelIterate.forEach(list3, new SumProcedure(sum3), new SumCombiner(sum3), 1, list3.size() / 2);
          Assert.assertEquals(15, sum3.getSum());
  
          IntegerSum sum4 = new IntegerSum(0);
          List<Integer> list4 = ParallelIterateTest.createIntegerList(35);
          ParallelIterate.forEach(list4, new SumProcedure(sum4), new SumCombiner(sum4));
          Assert.assertEquals(35, sum4.getSum());
  
          IntegerSum sum5 = new IntegerSum(0);
          MutableList<Integer> list5 = FastList.newList(list4);
          ParallelIterate.forEach(list5, new SumProcedure(sum5), new SumCombiner(sum5));
          Assert.assertEquals(35, sum5.getSum());
  
          IntegerSum sum6 = new IntegerSum(0);
          List<Integer> list6 = ParallelIterateTest.createIntegerList(40);
          ParallelIterate.forEach(list6, new SumProcedure(sum6), new SumCombiner(sum6), 1, list6.size() / 2);
          Assert.assertEquals(40, sum6.getSum());
  
          IntegerSum sum7 = new IntegerSum(0);
          MutableList<Integer> list7 = FastList.newList(list6);
          ParallelIterate.forEach(list7, new SumProcedure(sum7), new SumCombiner(sum7), 1, list6.size() / 2);
          Assert.assertEquals(40, sum7.getSum());
      }
  
      @Test
      public void testForEachImmutable()
      {
          IntegerSum sum1 = new IntegerSum(0);
          ImmutableList<Integer> list1 = Lists.immutable.ofAll(ParallelIterateTest.createIntegerList(16));
          ParallelIterate.forEach(list1, new SumProcedure(sum1), new SumCombiner(sum1), 1, list1.size() / 2);
          Assert.assertEquals(16, sum1.getSum());
  
          IntegerSum sum2 = new IntegerSum(0);
          ImmutableList<Integer> list2 = Lists.immutable.ofAll(ParallelIterateTest.createIntegerList(7));
          ParallelIterate.forEach(list2, new SumProcedure(sum2), new SumCombiner(sum2));
          Assert.assertEquals(7, sum2.getSum());
  
          IntegerSum sum3 = new IntegerSum(0);
          ImmutableList<Integer> list3 = Lists.immutable.ofAll(ParallelIterateTest.createIntegerList(15));
          ParallelIterate.forEach(list3, new SumProcedure(sum3), new SumCombiner(sum3), 1, list3.size() / 2);
          Assert.assertEquals(15, sum3.getSum());
  
          IntegerSum sum4 = new IntegerSum(0);
          ImmutableList<Integer> list4 = Lists.immutable.ofAll(ParallelIterateTest.createIntegerList(35));
          ParallelIterate.forEach(list4, new SumProcedure(sum4), new SumCombiner(sum4));
          Assert.assertEquals(35, sum4.getSum());
  
          IntegerSum sum5 = new IntegerSum(0);
          ImmutableList<Integer> list5 = FastList.newList(list4).toImmutable();
          ParallelIterate.forEach(list5, new SumProcedure(sum5), new SumCombiner(sum5));
          Assert.assertEquals(35, sum5.getSum());
  
          IntegerSum sum6 = new IntegerSum(0);
          ImmutableList<Integer> list6 = Lists.immutable.ofAll(ParallelIterateTest.createIntegerList(40));
          ParallelIterate.forEach(list6, new SumProcedure(sum6), new SumCombiner(sum6), 1, list6.size() / 2);
          Assert.assertEquals(40, sum6.getSum());
  
          IntegerSum sum7 = new IntegerSum(0);
          ImmutableList<Integer> list7 = FastList.newList(list6).toImmutable();
          ParallelIterate.forEach(list7, new SumProcedure(sum7), new SumCombiner(sum7), 1, list6.size() / 2);
          Assert.assertEquals(40, sum7.getSum());
      }
  
      @Test
      public void testForEachWithException()
      {
          Verify.assertThrows(
                  RuntimeException.class,
                  () -> ParallelIterate.forEach(
                          ParallelIterateTest.createIntegerList(5),
                          new PassThruProcedureFactory<>(EXCEPTION_PROCEDURE),
                          new PassThruCombiner<>(),
                          1,
                          5));
      }
  
      @Test
      public void testForEachWithIndexToArrayUsingFastListSerialPath()
      {
          Integer[] array = new Integer[200];
          FastList<Integer> list = (FastList<Integer>) Interval.oneTo(200).toList();
          Assert.assertTrue(ArrayIterate.allSatisfy(array, Predicates.isNull()));
          ParallelIterate.forEachWithIndex(list, (each, index) -> array[index] = each);
          Assert.assertArrayEquals(array, list.toArray(new Integer[]{}));
      }
  
      @Test
      public void testForEachWithIndexToArrayUsingFastList()
      {
          Integer[] array = new Integer[200];
          FastList<Integer> list = (FastList<Integer>) Interval.oneTo(200).toList();
          Assert.assertTrue(ArrayIterate.allSatisfy(array, Predicates.isNull()));
          ParallelIterate.forEachWithIndex(list, (each, index) -> array[index] = each, 10, 10);
          Assert.assertArrayEquals(array, list.toArray(new Integer[]{}));
      }
  
      @Test
      public void testForEachWithIndexToArrayUsingImmutableList()
      {
          Integer[] array = new Integer[200];
          ImmutableList<Integer> list = Interval.oneTo(200).toList().toImmutable();
          Assert.assertTrue(ArrayIterate.allSatisfy(array, Predicates.isNull()));
          ParallelIterate.forEachWithIndex(list, (each, index) -> array[index] = each, 10, 10);
          Assert.assertArrayEquals(array, list.toArray(new Integer[]{}));
      }
  
      @Test
      public void testForEachWithIndexToArrayUsingArrayList()
      {
          Integer[] array = new Integer[200];
          List<Integer> list = new ArrayList<>(Interval.oneTo(200));
          Assert.assertTrue(ArrayIterate.allSatisfy(array, Predicates.isNull()));
          ParallelIterate.forEachWithIndex(list, (each, index) -> array[index] = each, 10, 10);
          Assert.assertArrayEquals(array, list.toArray(new Integer[]{}));
      }
  
      @Test
      public void testForEachWithIndexToArrayUsingFixedArrayList()
      {
          Integer[] array = new Integer[10];
          List<Integer> list = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
          Assert.assertTrue(ArrayIterate.allSatisfy(array, Predicates.isNull()));
          ParallelIterate.forEachWithIndex(list, (each, index) -> array[index] = each, 1, 2);
          Assert.assertArrayEquals(array, list.toArray(new Integer[list.size()]));
      }
  
      @Test
      public void testForEachWithIndexException()
      {
          Verify.assertThrows(
                  RuntimeException.class,
                  () -> ParallelIterate.forEachWithIndex(
                          ParallelIterateTest.createIntegerList(5),
                          new PassThruObjectIntProcedureFactory<>(EXCEPTION_OBJECT_INT_PROCEDURE),
                          new PassThruCombiner<>(),
                          1,
                          5));
      }
  
      @Test
      public void select()
      {
          this.iterables.forEach(Procedures.cast(this::basicSelect));
      }
  
      private void basicSelect(RichIterable<Integer> iterable)
      {
          Collection<Integer> actual1 = ParallelIterate.select(iterable, Predicates.greaterThan(100));
          Collection<Integer> actual2 = ParallelIterate.select(iterable, Predicates.greaterThan(100), HashBag.<Integer>newBag(), 3, this.executor, true);
          Collection<Integer> actual3 = ParallelIterate.select(iterable, Predicates.greaterThan(100), true);
          RichIterable<Integer> expected = iterable.select(Predicates.greaterThan(100));
          Assert.assertEquals(expected.getClass().getSimpleName() + '/' + actual1.getClass().getSimpleName(), expected, actual1);
          Assert.assertEquals(expected.getClass().getSimpleName() + '/' + actual2.getClass().getSimpleName(), expected.toBag(), actual2);
          Assert.assertEquals(expected.getClass().getSimpleName() + '/' + actual3.getClass().getSimpleName(), expected, actual3);
      }
  
      @Test
      public void selectSortedSet()
      {
          RichIterable<Integer> iterable = Interval.oneTo(200).toSortedSet();
          Collection<Integer> actual1 = ParallelIterate.select(iterable, Predicates.greaterThan(100));
          Collection<Integer> actual2 = ParallelIterate.select(iterable, Predicates.greaterThan(100), true);
          RichIterable<Integer> expected = iterable.select(Predicates.greaterThan(100));
          Assert.assertSame(expected.getClass(), actual1.getClass());
          Assert.assertSame(expected.getClass(), actual2.getClass());
          Assert.assertEquals(expected.getClass().getSimpleName() + '/' + actual1.getClass().getSimpleName(), expected, actual1);
          Assert.assertEquals(expected.getClass().getSimpleName() + '/' + actual2.getClass().getSimpleName(), expected, actual2);
      }
  
      @Test
      public void count()
      {
          this.iterables.forEach(Procedures.cast(this::basicCount));
      }
  
      private void basicCount(RichIterable<Integer> iterable)
      {
          int actual1 = ParallelIterate.count(iterable, Predicates.greaterThan(100));
          int actual2 = ParallelIterate.count(iterable, Predicates.greaterThan(100), 6, this.executor);
          Assert.assertEquals(100, actual1);
          Assert.assertEquals(100, actual2);
      }
  
      @Test
      public void reject()
      {
          this.iterables.forEach(Procedures.cast(this::basicReject));
      }
  
      private void basicReject(RichIterable<Integer> iterable)
      {
          Collection<Integer> actual1 = ParallelIterate.reject(iterable, Predicates.greaterThan(100));
          Collection<Integer> actual2 = ParallelIterate.reject(iterable, Predicates.greaterThan(100), HashBag.<Integer>newBag(), 3, this.executor, true);
          Collection<Integer> actual3 = ParallelIterate.reject(iterable, Predicates.greaterThan(100), true);
          RichIterable<Integer> expected = iterable.reject(Predicates.greaterThan(100));
          Assert.assertEquals(expected.getClass().getSimpleName() + '/' + actual1.getClass().getSimpleName(), expected, actual1);
          Assert.assertEquals(expected.getClass().getSimpleName() + '/' + actual2.getClass().getSimpleName(), expected.toBag(), actual2);
          Assert.assertEquals(expected.getClass().getSimpleName() + '/' + actual3.getClass().getSimpleName(), expected, actual3);
      }
  
      @Test
      public void collect()
      {
          this.iterables.forEach(Procedures.cast(this::basicCollect));
      }
  
      private void basicCollect(RichIterable<Integer> iterable)
      {
          Collection<String> actual1 = ParallelIterate.collect(iterable, String::valueOf);
          Collection<String> actual2 = ParallelIterate.collect(iterable, String::valueOf, HashBag.<String>newBag(), 3, this.executor, false);
          Collection<String> actual3 = ParallelIterate.collect(iterable, String::valueOf, true);
          RichIterable<String> expected = iterable.collect(String::valueOf);
          Verify.assertSize(200, actual1);
          Verify.assertContains(String.valueOf(200), actual1);
          Assert.assertEquals(expected.getClass().getSimpleName() + '/' + actual1.getClass().getSimpleName(), expected, actual1);
          Assert.assertEquals(expected.getClass().getSimpleName() + '/' + actual2.getClass().getSimpleName(), expected.toBag(), actual2);
          Assert.assertEquals(expected.getClass().getSimpleName() + '/' + actual3.getClass().getSimpleName(), expected.toBag(), HashBag.newBag(actual3));
      }
  
      @Test
      public void collectIf()
      {
          this.iterables.forEach(Procedures.cast(this::basicCollectIf));
      }
  
      private void basicCollectIf(RichIterable<Integer> collection)
      {
          Predicate<Integer> greaterThan = Predicates.greaterThan(100);
          Collection<String> actual1 = ParallelIterate.collectIf(collection, greaterThan, String::valueOf);
          Collection<String> actual2 = ParallelIterate.collectIf(collection, greaterThan, String::valueOf, HashBag.<String>newBag(), 3, this.executor, true);
          Collection<String> actual3 = ParallelIterate.collectIf(collection, greaterThan, String::valueOf, HashBag.<String>newBag(), 3, this.executor, true);
          Bag<String> expected = collection.collectIf(greaterThan, String::valueOf).toBag();
          Verify.assertSize(100, actual1);
          Verify.assertNotContains(String.valueOf(90), actual1);
          Verify.assertNotContains(String.valueOf(210), actual1);
          Verify.assertContains(String.valueOf(159), actual1);
          Assert.assertEquals(expected.getClass().getSimpleName() + '/' + actual1.getClass().getSimpleName(), expected, HashBag.newBag(actual1));
          Assert.assertEquals(expected.getClass().getSimpleName() + '/' + actual2.getClass().getSimpleName(), expected, actual2);
          Assert.assertEquals(expected.getClass().getSimpleName() + '/' + actual3.getClass().getSimpleName(), expected, actual3);
      }
  
      @Test
      public void groupByWithInterval()
      {
          LazyIterable<Integer> iterable = Interval.oneTo(1000).concatenate(Interval.oneTo(1000)).concatenate(Interval.oneTo(1000));
          Multimap<String, Integer> expected = iterable.toBag().groupBy(String::valueOf);
          Multimap<String, Integer> expectedAsSet = iterable.toSet().groupBy(String::valueOf);
          Multimap<String, Integer> result1 = ParallelIterate.groupBy(iterable.toList(), String::valueOf, 100);
          Multimap<String, Integer> result2 = ParallelIterate.groupBy(iterable.toList(), String::valueOf);
          Multimap<String, Integer> result3 = ParallelIterate.groupBy(iterable.toSet(), String::valueOf, SynchronizedPutUnifiedSetMultimap.<String, Integer>newMultimap(), 100);
          Multimap<String, Integer> result4 = ParallelIterate.groupBy(iterable.toSet(), String::valueOf, SynchronizedPutUnifiedSetMultimap.<String, Integer>newMultimap());
          Multimap<String, Integer> result5 = ParallelIterate.groupBy(iterable.toSortedSet(), String::valueOf, SynchronizedPutUnifiedSetMultimap.<String, Integer>newMultimap(), 100);
          Multimap<String, Integer> result6 = ParallelIterate.groupBy(iterable.toSortedSet(), String::valueOf, SynchronizedPutUnifiedSetMultimap.<String, Integer>newMultimap());
          Multimap<String, Integer> result7 = ParallelIterate.groupBy(iterable.toBag(), String::valueOf, SynchronizedPutHashBagMultimap.<String, Integer>newMultimap(), 100);
          Multimap<String, Integer> result8 = ParallelIterate.groupBy(iterable.toBag(), String::valueOf, SynchronizedPutHashBagMultimap.<String, Integer>newMultimap());
          Multimap<String, Integer> result9 = ParallelIterate.groupBy(iterable.toList().toImmutable(), String::valueOf);
          Assert.assertEquals(expected, HashBagMultimap.newMultimap(result1));
          Assert.assertEquals(expected, HashBagMultimap.newMultimap(result2));
          Assert.assertEquals(expected, HashBagMultimap.newMultimap(result9));
          Assert.assertEquals(expectedAsSet, result3);
          Assert.assertEquals(expectedAsSet, result4);
          Assert.assertEquals(expectedAsSet, result5);
          Assert.assertEquals(expectedAsSet, result6);
          Assert.assertEquals(expected, result7);
          Assert.assertEquals(expected, result8);
      }
  
      @Test
      public void groupBy()
      {
          FastList<String> source = FastList.newListWith("Ted", "Sally", "Mary", "Bob", "Sara");
          Multimap<Character, String> result1 = ParallelIterate.groupBy(source, StringFunctions.firstLetter(), 1);
          Multimap<Character, String> result2 = ParallelIterate.groupBy(Collections.synchronizedList(source), StringFunctions.firstLetter(), 1);
          Multimap<Character, String> result3 = ParallelIterate.groupBy(Collections.synchronizedCollection(source), StringFunctions.firstLetter(), 1);
          Multimap<Character, String> result4 = ParallelIterate.groupBy(LazyIterate.adapt(source), StringFunctions.firstLetter(), 1);
          Multimap<Character, String> result5 = ParallelIterate.groupBy(new ArrayList<>(source), StringFunctions.firstLetter(), 1);
          Multimap<Character, String> result6 = ParallelIterate.groupBy(source.toSet(), StringFunctions.firstLetter(), 1);
          Multimap<Character, String> result7 = ParallelIterate.groupBy(source.toMap(Functions.getStringPassThru(), Functions.getStringPassThru()), StringFunctions.firstLetter(), 1);
          Multimap<Character, String> result8 = ParallelIterate.groupBy(source.toBag(), StringFunctions.firstLetter(), 1);
          Multimap<Character, String> result9 = ParallelIterate.groupBy(source.toImmutable(), StringFunctions.firstLetter(), 1);
          MutableMultimap<Character, String> expected = HashBagMultimap.newMultimap();
          expected.put('T', "Ted");
          expected.put('S', "Sally");
          expected.put('M', "Mary");
          expected.put('B', "Bob");
          expected.put('S', "Sara");
          Assert.assertEquals(expected, HashBagMultimap.newMultimap(result1));
          Assert.assertEquals(expected, HashBagMultimap.newMultimap(result2));
          Assert.assertEquals(expected, HashBagMultimap.newMultimap(result3));
          Assert.assertEquals(expected, HashBagMultimap.newMultimap(result4));
          Assert.assertEquals(expected, HashBagMultimap.newMultimap(result5));
          Assert.assertEquals(expected, HashBagMultimap.newMultimap(result6));
          Assert.assertEquals(expected, HashBagMultimap.newMultimap(result7));
          Assert.assertEquals(expected, HashBagMultimap.newMultimap(result8));
          Assert.assertEquals(expected, HashBagMultimap.newMultimap(result9));
          Verify.assertThrows(IllegalArgumentException.class, () -> ParallelIterate.groupBy(null, null, 1));
      }
  
      @Test
      public void aggregateInPlaceBy()
      {
          Procedure2<AtomicInteger, Integer> countAggregator = (aggregate, value) -> aggregate.incrementAndGet();
          List<Integer> list = Interval.oneTo(2000);
          MutableMap<String, AtomicInteger> aggregation =
                  ParallelIterate.aggregateInPlaceBy(list, EVEN_OR_ODD, AtomicInteger::new, countAggregator);
          Assert.assertEquals(1000, aggregation.get("Even").intValue());
          Assert.assertEquals(1000, aggregation.get("Odd").intValue());
          ParallelIterate.aggregateInPlaceBy(list, EVEN_OR_ODD, AtomicInteger::new, countAggregator, aggregation);
          Assert.assertEquals(2000, aggregation.get("Even").intValue());
          Assert.assertEquals(2000, aggregation.get("Odd").intValue());
      }
  
      @Test
      public void aggregateInPlaceByWithBatchSize()
      {
          MutableList<Integer> list = LazyIterate.adapt(Collections.nCopies(100, 1))
                  .concatenate(Collections.nCopies(200, 2))
                  .concatenate(Collections.nCopies(300, 3))
                  .toList()
                  .shuffleThis();
          MapIterable<String, AtomicInteger> aggregation =
                  ParallelIterate.aggregateInPlaceBy(list, String::valueOf, AtomicInteger::new, AtomicInteger::addAndGet, 50);
          Assert.assertEquals(100, aggregation.get("1").intValue());
          Assert.assertEquals(400, aggregation.get("2").intValue());
          Assert.assertEquals(900, aggregation.get("3").intValue());
      }
  
      @Test
      public void aggregateBy()
      {
          Function2<Integer, Integer, Integer> countAggregator = (aggregate, value) -> aggregate + 1;
          List<Integer> list = Interval.oneTo(20000);
          MutableMap<String, Integer> aggregation =
                  ParallelIterate.aggregateBy(list, EVEN_OR_ODD, () -> 0, countAggregator);
          Assert.assertEquals(10000, aggregation.get("Even").intValue());
          Assert.assertEquals(10000, aggregation.get("Odd").intValue());
          ParallelIterate.aggregateBy(list, EVEN_OR_ODD, () -> 0, countAggregator, aggregation);
          Assert.assertEquals(20000, aggregation.get("Even").intValue());
          Assert.assertEquals(20000, aggregation.get("Odd").intValue());
      }
  
      @Test
      public void sumByDouble()
      {
          Interval interval = Interval.oneTo(100000);
          ObjectDoubleMap<String> sumByCount = ParallelIterate.sumByDouble(interval, EVEN_OR_ODD, i -> 1.0d);
          Assert.assertEquals(50000.0, sumByCount.get("Even"), 0.0);
          Assert.assertEquals(50000.0, sumByCount.get("Odd"), 0.0);
          ObjectDoubleMap<String> sumByValue = ParallelIterate.sumByDouble(interval, EVEN_OR_ODD, Integer::doubleValue);
          Assert.assertEquals(interval.sumByDouble(EVEN_OR_ODD, Integer::doubleValue), sumByValue);
          ObjectDoubleMap<Integer> sumByValue2 = ParallelIterate.sumByDouble(interval, i -> i % 1000, Integer::doubleValue);
          Assert.assertEquals(interval.sumByDouble(i -> i % 1000, Integer::doubleValue), sumByValue2);
          Interval interval2 = Interval.oneTo(UNEVEN_COUNT_FOR_SUMBY);
          ObjectDoubleMap<String> sumByValue3 = ParallelIterate.sumByDouble(interval2, EVEN_OR_ODD, Integer::doubleValue);
          Assert.assertEquals(interval2.sumByDouble(EVEN_OR_ODD, Integer::doubleValue), sumByValue3);
          ObjectDoubleMap<Integer> sumByValue4 = ParallelIterate.sumByDouble(interval2, i -> i % 1000, Integer::doubleValue);
          Assert.assertEquals(interval2.sumByDouble(i -> i % 1000, Integer::doubleValue), sumByValue4);
          Interval small = Interval.oneTo(11);
          ObjectDoubleMap<String> smallSumByCount = ParallelIterate.sumByDouble(small, EVEN_OR_ODD, i -> 1.0d);
          Assert.assertEquals(5.0, smallSumByCount.get("Even"), 0.0);
          Assert.assertEquals(6.0, smallSumByCount.get("Odd"), 0.0);
      }
  
      @Test
      public void sumByDoubleConsistentRounding()
      {
          MutableList<Integer> group1 = Interval.oneTo(100_000).toList().shuffleThis();
          MutableList<Integer> group2 = Interval.fromTo(100_001, 200_000).toList().shuffleThis();
          MutableList<Integer> integers = Lists.mutable.withAll(group1);
          integers.addAll(group2);
          ObjectDoubleMap<Integer> result = ParallelIterate.<Integer, Integer>sumByDouble(
                  integers,
                  integer -> integer > 100_000 ? 2 : 1,
                  integer -> {
                      Integer i = integer > 100_000 ? integer - 100_000 : integer;
                      return 1.0d / (i.doubleValue() * i.doubleValue() * i.doubleValue() * i.doubleValue());
                  });
  
          Assert.assertEquals(
                  1.082323233711138,
                  result.get(1),
                  1.0e-15);
  
          Assert.assertEquals(
                  1.082323233711138,
                  result.get(2),
                  1.0e-15);
      }
  
      @Test
      public void sumByFloat()
      {
          Interval interval = Interval.oneTo(100000);
          ObjectDoubleMap<String> sumByCount = ParallelIterate.sumByFloat(interval, EVEN_OR_ODD, i -> 1.0f);
          Assert.assertEquals(50000.0, sumByCount.get("Even"), 0.0);
          Assert.assertEquals(50000.0, sumByCount.get("Odd"), 0.0);
          ObjectDoubleMap<String> sumByValue = ParallelIterate.sumByFloat(interval, EVEN_OR_ODD, Integer::floatValue);
          Assert.assertEquals(interval.sumByFloat(EVEN_OR_ODD, Integer::floatValue), sumByValue);
          ObjectDoubleMap<Integer> sumByValue2 = ParallelIterate.sumByFloat(interval, i -> i % 1000, Integer::floatValue);
          Assert.assertEquals(interval.sumByDouble(i -> i % 1000, Integer::doubleValue), sumByValue2);
          Interval interval2 = Interval.oneTo(UNEVEN_COUNT_FOR_SUMBY);
          ObjectDoubleMap<String> sumByValue3 = ParallelIterate.sumByFloat(interval2, EVEN_OR_ODD, Integer::floatValue);
          Assert.assertEquals(interval2.sumByFloat(EVEN_OR_ODD, Integer::floatValue), sumByValue3);
          ObjectDoubleMap<Integer> sumByValue4 = ParallelIterate.sumByFloat(interval2, i -> i % 1000, Integer::floatValue);
          Assert.assertEquals(interval2.sumByFloat(i -> i % 1000, Integer::floatValue), sumByValue4);
          Interval small = Interval.oneTo(11);
          ObjectDoubleMap<String> smallSumByCount = ParallelIterate.sumByFloat(small, EVEN_OR_ODD, i -> 1.0f);
          Assert.assertEquals(5.0, smallSumByCount.get("Even"), 0.0);
          Assert.assertEquals(6.0, smallSumByCount.get("Odd"), 0.0);
      }
  
      @Test
      public void sumByFloatConsistentRounding()
      {
          MutableList<Integer> group1 = Interval.oneTo(100_000).toList().shuffleThis();
          MutableList<Integer> group2 = Interval.fromTo(100_001, 200_000).toList().shuffleThis();
          MutableList<Integer> integers = Lists.mutable.withAll(group1);
          integers.addAll(group2);
          ObjectDoubleMap<Integer> result = ParallelIterate.<Integer, Integer>sumByFloat(
                  integers,
                  integer -> integer > 100_000 ? 2 : 1,
                  integer -> {
                      Integer i = integer > 100_000 ? integer - 100_000 : integer;
                      return 1.0f / (i.floatValue() * i.floatValue() * i.floatValue() * i.floatValue());
                  });
  
          // The test only ensures the consistency/stability of rounding. This is not meant to test the "correctness" of the float calculation result.
          // Indeed the lower bits of this calculation result are always incorrect due to the information loss of original float values.
          Assert.assertEquals(
                  1.082323233761663,
                  result.get(1),
                  1.0e-15);
  
          Assert.assertEquals(
                  1.082323233761663,
                  result.get(2),
                  1.0e-15);
      }
  
      @Test
      public void sumByLong()
      {
          Interval interval = Interval.oneTo(100000);
          ObjectLongMap<String> sumByCount = ParallelIterate.sumByLong(interval, EVEN_OR_ODD, i -> 1L);
          Assert.assertEquals(50000, sumByCount.get("Even"));
          Assert.assertEquals(50000, sumByCount.get("Odd"));
          ObjectLongMap<String> sumByValue = ParallelIterate.sumByLong(interval, EVEN_OR_ODD, Integer::longValue);
          Assert.assertEquals(interval.sumByLong(EVEN_OR_ODD, Integer::longValue), sumByValue);
          ObjectLongMap<Integer> sumByValue2 = ParallelIterate.sumByLong(interval, i -> i % 1000, Integer::longValue);
          Assert.assertEquals(interval.sumByLong(i -> i % 1000, Integer::longValue), sumByValue2);
          Interval interval2 = Interval.oneTo(UNEVEN_COUNT_FOR_SUMBY);
          ObjectLongMap<String> sumByValue3 = ParallelIterate.sumByLong(interval2, EVEN_OR_ODD, Integer::longValue);
          Assert.assertEquals(interval2.sumByLong(EVEN_OR_ODD, Integer::longValue), sumByValue3);
          ObjectLongMap<Integer> sumByValue4 = ParallelIterate.sumByLong(interval2, i -> i % 1000, Integer::longValue);
          Assert.assertEquals(interval2.sumByLong(i -> i % 1000, Integer::longValue), sumByValue4);
          Interval small = Interval.oneTo(11);
          ObjectLongMap<String> smallSumByCount = ParallelIterate.sumByLong(small, EVEN_OR_ODD, i -> 1L);
          Assert.assertEquals(5.0, smallSumByCount.get("Even"), 0.0);
          Assert.assertEquals(6.0, smallSumByCount.get("Odd"), 0.0);
      }
  
      @Test
      public void sumByInt()
      {
          Interval interval = Interval.oneTo(100000);
          ObjectLongMap<String> sumByCount = ParallelIterate.sumByInt(interval, EVEN_OR_ODD, i -> 1);
          Assert.assertEquals(50000, sumByCount.get("Even"));
          Assert.assertEquals(50000, sumByCount.get("Odd"));
          ObjectLongMap<String> sumByValue = ParallelIterate.sumByInt(interval, EVEN_OR_ODD, Integer::intValue);
          Assert.assertEquals(interval.sumByInt(EVEN_OR_ODD, Integer::intValue), sumByValue);
          ObjectLongMap<Integer> sumByValue2 = ParallelIterate.sumByInt(interval, i -> i % 1000, Integer::intValue);
          Assert.assertEquals(interval.sumByInt(i -> i % 1000, Integer::intValue), sumByValue2);
          Interval interval2 = Interval.oneTo(UNEVEN_COUNT_FOR_SUMBY);
          ObjectLongMap<String> sumByValue3 = ParallelIterate.sumByInt(interval2, EVEN_OR_ODD, Integer::intValue);
          Assert.assertEquals(interval2.sumByInt(EVEN_OR_ODD, Integer::intValue), sumByValue3);
          ObjectLongMap<Integer> sumByValue4 = ParallelIterate.sumByInt(interval2, i -> i % 1000, Integer::intValue);
          Assert.assertEquals(interval2.sumByInt(i -> i % 1000, Integer::intValue), sumByValue4);
          Interval small = Interval.oneTo(11);
          ObjectLongMap<String> smallSumByCount = ParallelIterate.sumByInt(small, EVEN_OR_ODD, i -> 1);
          Assert.assertEquals(5.0, smallSumByCount.get("Even"), 0.0);
          Assert.assertEquals(6.0, smallSumByCount.get("Odd"), 0.0);
      }
  
      @Test
      public void sumByBigDecimal()
      {
          MutableList<BigDecimal> list = Interval.oneTo(100000).collect(BigDecimal::new).toList().shuffleThis();
          MutableMap<String, BigDecimal> sumByCount = ParallelIterate.sumByBigDecimal(list, EVEN_OR_ODD_BD, bd -> new BigDecimal(1L));
          Assert.assertEquals(BigDecimal.valueOf(50000L), sumByCount.get("Even"));
          Assert.assertEquals(BigDecimal.valueOf(50000L), sumByCount.get("Odd"));
          MutableMap<String, BigDecimal> sumByValue = ParallelIterate.sumByBigDecimal(list, EVEN_OR_ODD_BD, bd -> bd);
          Assert.assertEquals(Iterate.sumByBigDecimal(list, EVEN_OR_ODD_BD, bd -> bd), sumByValue);
          MutableMap<Integer, BigDecimal> sumByValue2 = ParallelIterate.sumByBigDecimal(list, bd -> bd.intValue() % 1000, bd -> bd);
          Assert.assertEquals(Iterate.sumByBigDecimal(list, bd -> bd.intValue() % 1000, bd -> bd), sumByValue2);
          MutableList<BigDecimal> list2 = Interval.oneTo(UNEVEN_COUNT_FOR_SUMBY).collect(BigDecimal::new).toList();
          MutableMap<String, BigDecimal> sumByValue3 = ParallelIterate.sumByBigDecimal(list2, EVEN_OR_ODD_BD, bd -> bd);
          Assert.assertEquals(Iterate.sumByBigDecimal(list2, EVEN_OR_ODD_BD, bd -> bd), sumByValue3);
          MutableMap<Integer, BigDecimal> sumByValue4 = ParallelIterate.sumByBigDecimal(list2, bd -> bd.intValue() % 1000, bd -> bd);
          Assert.assertEquals(Iterate.sumByBigDecimal(list2, bd -> bd.intValue() % 1000, bd -> bd), sumByValue4);
          Interval small = Interval.oneTo(11);
          MutableMap<String, BigDecimal> smallSumByCount = ParallelIterate.sumByBigDecimal(small, EVEN_OR_ODD, i -> BigDecimal.valueOf(1L));
          Assert.assertEquals(new BigDecimal(5), smallSumByCount.get("Even"));
          Assert.assertEquals(new BigDecimal(6), smallSumByCount.get("Odd"));
      }
  
      @Test
      public void sumByBigInteger()
      {
          MutableList<BigInteger> list = Interval.oneTo(100000).collect(Object::toString).collect(BigInteger::new).toList().shuffleThis();
          MutableMap<String, BigInteger> sumByCount = ParallelIterate.sumByBigInteger(list, EVEN_OR_ODD_BI, bi -> BigInteger.valueOf(1L));
          Assert.assertEquals(BigInteger.valueOf(50000L), sumByCount.get("Even"));
          Assert.assertEquals(BigInteger.valueOf(50000L), sumByCount.get("Odd"));
          MutableMap<String, BigInteger> sumByValue = ParallelIterate.sumByBigInteger(list, EVEN_OR_ODD_BI, bi -> bi);
          Assert.assertEquals(Iterate.sumByBigInteger(list, EVEN_OR_ODD_BI, bi -> bi), sumByValue);
          MutableMap<Integer, BigInteger> sumByValue2 = ParallelIterate.sumByBigInteger(list, bi -> bi.intValue() % 1000, bi -> bi);
          Assert.assertEquals(Iterate.sumByBigInteger(list, bi -> bi.intValue() % 1000, bi -> bi), sumByValue2);
          MutableList<BigInteger> list2 = Interval.oneTo(UNEVEN_COUNT_FOR_SUMBY).collect(Object::toString).collect(BigInteger::new).toList();
          MutableMap<String, BigInteger> sumByValue3 = ParallelIterate.sumByBigInteger(list2, EVEN_OR_ODD_BI, bi -> bi);
          Assert.assertEquals(Iterate.sumByBigInteger(list2, EVEN_OR_ODD_BI, bi -> bi), sumByValue3);
          MutableMap<Integer, BigInteger> sumByValue4 = ParallelIterate.sumByBigInteger(list2, bi -> bi.intValue() % 1000, bi -> bi);
          Assert.assertEquals(Iterate.sumByBigInteger(list2, bi -> bi.intValue() % 1000, bi -> bi), sumByValue4);
          Interval small = Interval.oneTo(11);
          MutableMap<String, BigInteger> smallSumByCount = ParallelIterate.sumByBigInteger(small, EVEN_OR_ODD, i -> BigInteger.valueOf(1L));
          Assert.assertEquals(new BigInteger("5"), smallSumByCount.get("Even"));
          Assert.assertEquals(new BigInteger("6"), smallSumByCount.get("Odd"));
      }
  
      @Test
      public void aggregateByWithBatchSize()
      {
          Function2<Integer, Integer, Integer> sumAggregator = (aggregate, value) -> aggregate + value;
          MutableList<Integer> list = LazyIterate.adapt(Collections.nCopies(1000, 1))
                  .concatenate(Collections.nCopies(2000, 2))
                  .concatenate(Collections.nCopies(3000, 3))
                  .toList()
                  .shuffleThis();
          MapIterable<String, Integer> aggregation =
                  ParallelIterate.aggregateBy(list, String::valueOf, () -> 0, sumAggregator, 100);
          Assert.assertEquals(1000, aggregation.get("1").intValue());
          Assert.assertEquals(4000, aggregation.get("2").intValue());
          Assert.assertEquals(9000, aggregation.get("3").intValue());
      }
  
      private static List<Integer> createIntegerList(int size)
      {
          return Collections.nCopies(size, Integer.valueOf(1));
      }
  
      @Test
      public void flatCollect()
      {
          this.iterables.forEach(Procedures.cast(this::basicFlatCollect));
      }
  
      private void basicFlatCollect(RichIterable<Integer> iterable)
      {
          Collection<String> actual1 = ParallelIterate.flatCollect(iterable, INT_TO_TWO_STRINGS);
          Collection<String> actual2 = ParallelIterate.flatCollect(iterable, INT_TO_TWO_STRINGS, HashBag.<String>newBag(), 3, this.executor, false);
          Collection<String> actual3 = ParallelIterate.flatCollect(iterable, INT_TO_TWO_STRINGS, true);
          RichIterable<String> expected1 = iterable.flatCollect(INT_TO_TWO_STRINGS);
          RichIterable<String> expected2 = iterable.flatCollect(INT_TO_TWO_STRINGS, HashBag.<String>newBag());
          Verify.assertContains(String.valueOf(200), actual1);
          Assert.assertEquals(expected1.getClass().getSimpleName() + '/' + actual1.getClass().getSimpleName(), expected1, actual1);
          Assert.assertEquals(expected2.getClass().getSimpleName() + '/' + actual2.getClass().getSimpleName(), expected2, actual2);
          Assert.assertEquals(expected1.getClass().getSimpleName() + '/' + actual3.getClass().getSimpleName(), expected1, actual3);
      }
  
      public static final class IntegerSum
      {
          private int sum;
  
          public IntegerSum(int newSum)
          {
              this.sum = newSum;
          }
  
          public IntegerSum add(int value)
          {
              this.sum += value;
              return this;
          }
  
          public int getSum()
          {
              return this.sum;
          }
      }
  
      public static final class SumProcedure
              implements Procedure<Integer>, Function2<IntegerSum, Integer, IntegerSum>, ProcedureFactory<SumProcedure>
      {
          private static final long serialVersionUID = 1L;
  
          private final IntegerSum sum;
  
          public SumProcedure(IntegerSum newSum)
          {
              this.sum = newSum;
          }
  
          @Override
          public SumProcedure create()
          {
              return new SumProcedure(new IntegerSum(0));
          }
  
          @Override
          public IntegerSum value(IntegerSum s1, Integer s2)
          {
              return s1.add(s2);
          }
  
          @Override
          public void value(Integer object)
          {
              this.sum.add(object);
          }
  
          public int getSum()
          {
              return this.sum.getSum();
          }
      }
  
      public static final class SumCombiner extends AbstractProcedureCombiner<SumProcedure>
      {
          private static final long serialVersionUID = 1L;
          private final IntegerSum sum;
  
          public SumCombiner(IntegerSum initialSum)
          {
              super(true);
              this.sum = initialSum;
          }
  
          @Override
          public void combineOne(SumProcedure sumProcedure)
          {
              this.sum.add(sumProcedure.getSum());
          }
      }
  
      @Test
      public void classIsNonInstantiable()
      {
          Verify.assertClassNonInstantiable(ParallelIterate.class);
      }
  }
  