0

My current code:

List<Float> totalAmounts = sources.stream().collect(Collectors.groupingBy(Cause::getAmount, Collectors.counting())) .entrySet().stream().sorted(entryComp).map(Map.Entry::getValue).limit(3).toList(); 

The idea, is that it adds up all of Cause.getAmount(), which is a float into a list of max 3 elements The Cause class constructor looks like this, Cause has all getters and setters.

public Cause(String name, int time, float amount) {} 

For example, the sources list would look like

new Cause("Name", 34, 15.53F); new Cause("Name", 636, 2.12F); new Cause("Name", 2345, 3.14F); new Cause("Name", 568, 9F); new Cause("OtherName", 10, 5.55F); new Cause("OtherName", 5324, 0.27F); new Cause("OtherName", 1, 6.21F); new Cause("RealName", 921, 7.05F); new Cause("RealName", 3899, 11.11F); new Cause("RealName", 1782, 8.38F); new Cause("BadName", 234, 1.01F); new Cause("BadName", 581, 6.31F); 

and the expected result of totalAmounts as the full <K, V> would be

(4, 29.79F) (3, 12.03F) (3, 26.54F) 

(I am aware I am only pulling the Entry::getValue ) The key is the number of equal strings The value is the sum of all objects with the equal strings' last constructor parameter (It must be either a double or float) Here's my whole code.

List<Cause> sources = new CopyOnWriteArrayList<>(); private String topThree() { StringBuilder result = new StringBuilder(); Comparator<Map.Entry<String, Long>> entryComparator = Collections.reverseOrder(Map.Entry.comparingByValue()); Comparator<Map.Entry<Float, Double>> entryComp = Collections.reverseOrder(Map.Entry.comparingByValue()); List<String> most = sources.stream().collect(Collectors.groupingBy(Cause::getName, Collectors.counting())) .entrySet().stream().sorted(entryComparator).map(Map.Entry::getKey).limit(3).toList(); List<Long> mostTimes = sources.stream().collect(Collectors.groupingBy(Cause::getName, Collectors.counting())) .entrySet().stream().sorted(entryComparator).map(Map.Entry::getValue).limit(3).toList(); List<Float> total = sources.stream().collect(Collectors.groupingBy(Cause::getAmount, Collectors.counting())) .entrySet().stream().sorted(entryComp).map(Map.Entry::getValue).limit(3).toList(); for (int i = 0; i < most.size(); i++) { try { result.append((i+1) + ". " + most.get(i) + " " + mostTimes.get(i) + " " + total.get(i) + "\n"); } catch (ArrayIndexOutOfBoundsException ignored) {} } return result.toString(); } @Getter @Setter private static final class Cause { private Source source; private String name; private float amount; private int tickRecordedOn; public Cause(Source source, String name, float amount, int tickRecordedOn) { this.source = source; this.name = name; this.amount = amount; this.tickRecordedOn = tickRecordedOn;// Used for syncing, nothing else } } 

The idea behind the topThree method is to return

(Cause Name) (Amount of times the Cause name is in the list) (The sum of the Cause amount) 

Basically condensing the list of Cause into a single string that is Cause, just added up each value.

10
  • you should group by name and then add corresponding amount in map value, as per your expectation you key is count which can occur more than once which is not possible in map. map cant have duplicate key . Map<String, Double> result = causes.stream() .collect(Collectors.groupingBy(Cause::getName, Collectors.summingDouble(Cause::getAmount))); System.out.println("Count of names and sum of float values:"); result.forEach((name, sum) -> System.out.println(name + ": " + sum)); } Commented Dec 15, 2023 at 6:14
  • Would sending my whole code help? I know I kinda explained it poorly, but the code should speak for itself. Commented Dec 15, 2023 at 6:16
  • ya that will make some sense its not clear . mention what output you are getting and what you want Commented Dec 15, 2023 at 6:18
  • The question is unclear, please explain clearly what you want to achieve and what goes wrong. First you talk about (and do) grouping and counting by the amount, then you talk about grouping by name and summing the amount. Which of those do you want to do? Both? Please explain. Commented Dec 15, 2023 at 6:18
  • Also, don't post your entire code, it just distracts from the problem. Condense it to a minimal, reproducible example. What you have currently is enough as long you explain clearly what you want to achieve. Commented Dec 15, 2023 at 6:23

1 Answer 1

3

Note that your two statements

List<String> most = sources.stream().collect(Collectors.groupingBy(Cause::getName, Collectors.counting())) .entrySet().stream().sorted(entryComparator).map(Map.Entry::getKey).limit(3).toList(); List<Long> mostTimes = sources.stream().collect(Collectors.groupingBy(Cause::getName, Collectors.counting())) .entrySet().stream().sorted(entryComparator).map(Map.Entry::getValue).limit(3).toList(); 

perform the same (almost) operation two times, dropping the information needed by the other result in the .map(Map.Entry::getKey) resp. .map(Map.Entry::getValue) step.

So, remove this map step and collect the information in a result containing all information, e.g.

Map<String,Long> mostTimes = sources.stream() .collect(Collectors.groupingBy(Cause::getName, Collectors.counting())) .entrySet().stream() .sorted(Collections.reverseOrder(Map.Entry.comparingByValue())).limit(3) .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)); 

This map will not be ordered but contain the top three elements.

Now, to integrate the missing sum of amounts, change Collectors.counting() to Collectors.summarizingDouble(Cause::getAmount). Since this changes the result’s value from Long to DoubleSummaryStatistics, the comparator also needs to be changed:

Map<String, DoubleSummaryStatistics> mostTimes = sources.stream() .collect(Collectors.groupingBy(Cause::getName, Collectors.summarizingDouble(Cause::getAmount))) .entrySet().stream() .sorted(Collections.reverseOrder(Map.Entry.comparingByValue( Comparator.comparingDouble(DoubleSummaryStatistics::getSum)))).limit(3) .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)); 

Since the intended final result is a String, one option is to change the collector to create a String in the first place. This would also respect the order established by the sorted step:

private String topThree() { return sources.stream() .collect(Collectors.groupingBy(Cause::getName, Collectors.summarizingDouble(Cause::getAmount))) .entrySet().stream() .sorted(Collections.reverseOrder(Map.Entry.comparingByValue( Comparator.comparingDouble(DoubleSummaryStatistics::getSum)))).limit(3) .map(entry -> entry.getKey() + " " + entry.getValue().getCount() + " " + entry.getValue().getSum()) .collect(Collectors.joining("\n")); } 

This will produce

Name 4 29.78999972343445 RealName 3 26.539999961853027 OtherName 3 12.030000239610672 

which might be an acceptable result if you can live without the preceding ranking number.

Otherwise, the solution will involve a bit more manual processing:

private String topThree() { return sources.stream() .collect(Collectors.groupingBy(Cause::getName, Collectors.summarizingDouble(Cause::getAmount))) .entrySet().stream() .collect(Collectors.collectingAndThen( Collectors.toCollection(ArrayList::new), list -> { list.sort(Map.Entry.comparingByValue( Comparator.comparingDouble(DoubleSummaryStatistics::getSum))); StringBuilder result = new StringBuilder(); for(int rank = 1, i = list.size()-1; i >= 0 && rank <= 3; rank++,i--) { result.append(rank).append(". ").append(list.get(i).getKey()) .append(" ").append(list.get(i).getValue().getCount()) .append(" ") .append(list.get(i).getValue().getSum()).append("\n"); } return result.toString(); })); } 

This will produce

1. Name 4 29.78999972343445 2. RealName 3 26.539999961853027 3. OtherName 3 12.030000239610672 

Since the sorted step implies buffering of all objects for sorting, typically in an array, I moved the sorting into the finisher function which has to process a list (wrapping an array) anyway.

It is possible to solve the task without collecting and sorting all elements, see for example this answer. However, such an optimization will only pay off if the number of groups is significantly larger than your limit, i.e. not in your example of selecting three out of four.

Sign up to request clarification or add additional context in comments.

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.