Уменьшение всех сумм

Во время Глубокой заморозки 2018 года казалось, что все в мире превратилось в лед и снег, включая небо. Даже солнце, казалось, уменьшилось, когда оно, дрожа, убегало от моей камеры.

Это заставило меня задуматься о различных видах сокращений, доступных в Java с помощью Коллекций Eclipse и Java Streams. Мне было интересно, сколько способов мы могли бы определить sum с помощью различных доступных методов.

Суммирование массива целых чисел

Давайте рассмотрим несколько способов суммирования значений в массиве int в Java.

Вот данные, которые мы будем использовать.

int[] ints = {1, 2, 3, 4, 5};
// This supplier will generate an IntStream on demand
IntStream intStream = Arrays.stream(ints);
// This creates an IntList from Eclipse Collections
IntList intList = IntLists.immutable.with(ints);

For Loop

int sumForLoop = 0;
for (int i = 0; i < ints.length; i++)
{
    sumForLoop += ints[i];
}
Assert.assertEquals(15, sumForLoop);

forEach (IntStream) / каждый (IntList)

// sumForEach will be effectively final
int[] sumForEach = {0};
intStream.forEach(e -> sumForEach[0] += e);
Assert.assertEquals(15, sumForEach[0]);
// sumEach will be effectively final
int[] sumEach = {0};
intList.each(e -> sumEach[0] += e);
Assert.assertEquals(15, sumEach[0]);

injectInto (IntList)

// injectInto boxes on IntList as there is no primitive version
int sumInject = 
    intList.injectInto(Integer.valueOf(0), Integer::sum).intValue();
Assert.assertEquals(15, sumInject);

уменьшить (IntStream)

// reduce does not box on IntStream
int sumReduce = 
    intStream.reduce(Integer::sum).getAsInt();
Assert.assertEquals(15, sumReduce);

сумма (IntStream / IntList)

int sum1 = intStream.sum();
Assert.assertEquals(15, sum1);

long sum2 = intList.sum();
Assert.assertEquals(15, sum2);

Ясно, что sum методы, доступные на IntStream и IntList, являются простейшими решениями. Незначительная разница с IntList заключается в том, что результат расширяется до long, что означает, что вы можете добавлять очень большие целые числа без переполнения.

Обобщение массива целых чисел

Когда мы подводим итоги с использованием класса IntSummaryStatistics, который был добавлен в Java 8, мы получаем одновременно вычисляемые count, sum, min, max и average. Это избавляет вас от многократных повторений. Мы будем использовать те же данные, что и раньше.

For Loop

IntSummaryStatistics statsForLoop = new IntSummaryStatistics();
for (int i = 0; i < ints.length; i++)
{
    statsForLoop.accept(ints[i]);
}
Assert.assertEquals(15, statsForLoop.getSum());
Assert.assertEquals(1, statsForLoop.getMin());
Assert.assertEquals(5, statsForLoop.getMax());

forEach (IntStream) / каждый (IntList)

IntSummaryStatistics statsForEach = new IntSummaryStatistics();
intStream.forEach(statsForEach::accept);

Assert.assertEquals(15, statsForEach.getSum());
Assert.assertEquals(1, statsForEach.getMin());
Assert.assertEquals(5, statsForEach.getMax());

IntSummaryStatistics statsEach = new IntSummaryStatistics();
intList.each(statsEach::accept);

Assert.assertEquals(15, statsEach.getSum());
Assert.assertEquals(1, statsEach.getMin());
Assert.assertEquals(5, statsEach.getMax());

injectInto (IntList)

IntSummaryStatistics statsInject =
        intList.injectInto(
            new IntSummaryStatistics(), 
            (iss, each) -> {iss.accept(each); return iss;});

Assert.assertEquals(15, statsInject.getSum());
Assert.assertEquals(1, statsInject.getMin());
Assert.assertEquals(5, statsInject.getMax());

собирать (IntStream)

IntSummaryStatistics statsCollect =
        intStream.collect(
            IntSummaryStatistics::new, 
            IntSummaryStatistics::accept, 
            IntSummaryStatistics::combine);

Assert.assertEquals(15, statsCollect.getSum());
Assert.assertEquals(1, statsCollect.getMin());
Assert.assertEquals(5, statsCollect.getMax());

Примечание. Я не мог использовать reduce, потому что оба параметра должны быть одного типа. Вместо этого мне пришлось использовать collect, что является изменяемым сокращением. Метод collect в примитивных потоках не принимает Collector, но вместо этого принимает Supplier, ObjectIntConsumer (аккумулятор) и BiConsumer (объединитель).

summaryStatistics (IntStream / IntList)

IntSummaryStatistics stats1 = intStream.summaryStatistics();

Assert.assertEquals(15, stats1.getSum());
Assert.assertEquals(1, stats1.getMin());
Assert.assertEquals(5, stats1.getMax());

IntSummaryStatistics stats2 = intList.summaryStatistics();

Assert.assertEquals(15, stats2.getSum());
Assert.assertEquals(1, stats2.getMin());
Assert.assertEquals(5, stats2.getMax());

Опять же, summaryStatistics методы - самые простые решения.

Суммирование длин массива строк

Допустим, мы хотим просуммировать длины строк в массиве. Этот подход можно использовать для суммирования любого атрибута int объекта.

Вот данные, которые мы будем использовать.

String[] words = {"The", "Quick", "Brown", "Fox", "jumps", "over", "the", "lazy", "dog"};

Stream<String> stream = Stream.of(words);
ImmutableList<String> list = Lists.immutable.with(words);

Для цикла

int sumForLoop = 0;
for (int i = 0; i < words.length; i++)
{
    sumForLoop += words[i].length();
}
Assert.assertEquals(35, sumForLoop);

Для каждого (Stream) / каждого (ImmutableList)

int[] sumForEach = {0};
stream.forEach(e -> sumForEach[0] += e.length());
Assert.assertEquals(35, sumForEach[0]);

int[] sumEach = {0};
list.each(e -> sumEach[0] += e.length());
Assert.assertEquals(35, sumEach[0]);

collectInt (_35 _) + injectInto (IntList)

int sumInject = list
        .collectInt(String::length)
        .injectInto(Integer.valueOf(0), Integer::sum)
        .intValue();
Assert.assertEquals(35, sumInject);

сбор (Stream) + уменьшение (Collectors)

int sumReducing = 
    stream.collect(Collectors.reducing(0,
                String::length,
                Integer::sum)).intValue();
Assert.assertEquals(35, sumReduce);

mapToInt (Stream) + Уменьшить (IntStream)

int sumReduce = stream
        .mapToInt(String::length)
        .reduce(Integer::sum)
        .getAsInt();
Assert.assertEquals(35, sumReduce);

mapToInt (Stream) + сумма (IntStream)

int sum1 = stream
        .mapToInt(String::length)
        .sum();
Assert.assertEquals(35, sum1);

collectInt (ImmutableList) + сумма (IntList)

long sum2 = list
        .collectInt(String::length)
        .sum();
Assert.assertEquals(35, sum2);

collect (Stream) + summingInt (Collectors)

Integer summingInt = stream
    .collect(Collectors.summingInt(String::length));
Assert.assertEquals(35, summingInt.intValue());

sumOfInt (ImmutableList)

long sumOfInt = list.sumOfInt(String::length);
Assert.assertEquals(35, sumOfInt);

Я думаю, что в этих примерах sumOfInt является самым простым решением.

Суммирование длин строк, сгруппированных по первому символу

В этой задаче мы сгруппируем строки по их первому символу и sum длине строк для каждого символа. Я предпочитаю использовать здесь примитивные карты для группировки, если это возможно.

Вот данные.

String[] words = {"The", "Quick", "Brown", "Fox", "jumps", "over", "the", "lazy", "dog"};

Stream<String> stream = Stream.of(words).map(String::toLowerCase);
ImmutableList<String> list =
    Lists.immutable.with(words).collect(String::toLowerCase);

Строки Stream и ImmutableList преобразуются в нижний регистр с использованием map и collect соответственно. Мы сделаем это вручную в примере цикла for.

Для цикла

MutableCharIntMap sumByForLoop = new CharIntHashMap();
for (int i = 0; i < words.length; i++)
{
    String word = words[i].toLowerCase();
    sumByForLoop.addToValue(word.charAt(0), word.length());
}
Assert.assertEquals(35, sumByForLoop.values().sum());
Assert.assertEquals(6, sumByForLoop.get('t'));

для каждого (Stream) / каждого (ImmutableList)

MutableCharIntMap sumByForEach = new CharIntHashMap();
stream.forEach(
    e -> sumByForEach.addToValue(e.charAt(0), e.length()));
Assert.assertEquals(35, sumByForEach.values().sum());
Assert.assertEquals(6, sumByForEach.get('t'));

MutableCharIntMap sumByEach = new CharIntHashMap();
list.each(
    e -> sumByEach.addToValue(e.charAt(0), e.length()));
Assert.assertEquals(35, sumByEach.values().sum());
Assert.assertEquals(6, sumByEach.get('t'));

injectInto (ImmutableList)

MutableCharIntMap sumByInject =
        list.injectInto(
                new CharIntHashMap(),
                (map, each) -> {
                    map.addToValue(each.charAt(0), each.length());
                    return map;
                });
Assert.assertEquals(35, sumByInject.values().sum());
Assert.assertEquals(6, sumByInject.get('t'));

уменьшить (Stream)

MutableCharIntMap sumByReduce = stream
        .reduce(
                new CharIntHashMap(),
                (map, e) -> {
                    map.addToValue(e.charAt(0), e.length());
                    return map;
                },
                (map1, map2) -> {
                    map1.putAll(map2);
                    return map1;
                });

Assert.assertEquals(35, sumByReduce.values().sum());
Assert.assertEquals(6, sumByReduce.get('t'));

aggregateBy (ImmutableList)

ImmutableMap<Character, Long> aggregateBy = list.aggregateBy(
        word -> word.charAt(0),
        () -> new Long(0),
        (sum, each) -> sum + each.length());
Assert.assertEquals(35,
    aggregateBy.valuesView().sumOfLong(Long::longValue));
Assert.assertEquals(6, aggregateBy.get('t').longValue());

aggregateInPlaceBy (ImmutableList)

ImmutableMap<Character, LongAdder> aggregateInPlaceBy = 
        list.aggregateInPlaceBy(
                word -> word.charAt(0),
                LongAdder::new,
                (adder, each) -> adder.add(each.length()));
Assert.assertEquals(35,
    aggregateInPlaceBy.valuesView()
        .sumOfLong(LongAdder::longValue));
Assert.assertEquals(6, aggregateInPlaceBy.get('t').longValue());

собирать (Stream)

MutableCharIntMap sumByCollect = stream.collect(
        CharIntHashMap::new,
        (map, e) -> map.addToValue(e.charAt(0), e.length()),
        CharIntHashMap::putAll);

Assert.assertEquals(35, sumByCollect.values().sum());
Assert.assertEquals(6, sumByCollect.get('t'));

collect (Stream) + groupingBy (Collectors) + summingInt (Collectors)

Map<Character, Integer> sumByCollectSummingInt =
        stream.collect(Collectors.groupingBy(
                        word -> word.charAt(0),
                        Collectors.summingInt(String::length)));
Assert.assertEquals(
    35,
    sumByCollectSummingInt
        .values().stream().mapToInt(Integer::intValue).sum());
Assert.assertEquals(
    Integer.valueO(6), sumByCollectSummingInt.get('t'));

collect (Stream) + sumByInt (Collectors2)

ObjectLongMap<Character> sumByCollectors2 =
        stream.collect(
                Collectors2.sumByInt(
                    word -> word.charAt(0), String::length));
Assert.assertEquals(35, sumByCollectors2.values().sum());
Assert.assertEquals(6, sumByCollectors2.get('t'));

reduceInPlace (_66 _) + sumByInt (Collectors2)

ObjectLongMap<Character> reduceInPlaceCollectors2 =
        list.reduceInPlace(
                Collectors2.sumByInt(
                    e -> e.charAt(0), String::length));
Assert.assertEquals(35, reduceInPlaceCollectors2.values().sum());
Assert.assertEquals(6, reduceInPlaceCollectors2.get('t'));

sumByInt (ImmutableList)

ObjectLongMap<Character> sumByInt =
        list.sumByInt(e -> e.charAt(0), String::length);
Assert.assertEquals(35, sumByInt.values().sum());
Assert.assertEquals(6, sumByInt.get('t'));

Самое простое решение - sumByInt.

Заключение

Мы рассмотрели множество различных подходов, которые вы можете использовать для sum или summarize значений с помощью Java и Eclipse Collections. В случае суммирования использование метода с суммой в имени, вероятно, даст вам самое простое решение. Вы можете решить практически любую проблему, используя такие методы, как injectInto и reduceInPlace (Eclipse Collections) или collect (Java Stream). Такие методы, как reduce, менее полезны, когда ваш результат должен отличаться от вашего ввода. Такие методы, как aggregateBy и aggregateInPlaceBy, дают более конкретный результат, чем collect, потому что они всегда возвращают Map. Использование Collectors2 может быть полезно, если вы хотите перебрать Stream и легко получить результат примитивной карты, используя collect.

Я руководитель проекта и ответственный за проект OSS Коллекции Eclipse в Eclipse Foundation. Eclipse Collections открыта для пожертвований. Если вам нравится библиотека, вы можете сообщить нам об этом, отметив ее на GitHub.