Уменьшение всех сумм
Во время Глубокой заморозки 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.