1

I have a stream of records and I need to find all the records with minimum value of field. Lets say I have following Person record record Person(String name, BigDecimal loanAmount, LocalDate dateOfBirth) {}. I need to find all the persons with minimum loanAmount. I found two ways to do this

  1. I can find the minimum loanAmount using min operator and then iterate over the list once more to filter and collect all the Persons with min loanAmount
  2. I can use Collectors.groupingBy to create a Map<BigDecimal(loanAmount), List<Person>> and then get the List from Map with min loanAmount.

I am wondering if I could collect only the Persons with minimum loanAmount with just single pass without having to collect other Person in map which I dont need.
Note: I am getting the Persons through a web API as json and then I am converting those to java records and then I am doing all this processing so I am wondering if there is an elegant way of doing this in one pass.

Thank you for reading and answering the question in advance.

2 Answers2

0

Using TreeMap it will work:

List< Person > personWithLowestLoanAmount = persons.stream()
        .collect(Collectors.groupingBy(Person::getLoanAmount, TreeMap::new, Collectors.toList()))
        .firstEntry()
        .getValue();

May be this another approach help you:

Map<Integer, List<Person>> temp = persons.stream().collect(groupingBy(Person::getLoanAmount));

List<Person> allPersonWithLowestLoanAmount  = temp.entrySet().stream()
        .min(Comparator.comparing(Map.Entry::getKey))
        .map(Map.Entry::getValue)
        .orElse(Collections.emptyList());
Jimmy
  • 995
  • 9
  • 18
  • Thanks for the answer, I first tried the TreeMap and it surely works, but this way I end up creating a map with all the records which I am trying to avoid. – Hrishikesh Joshi Sep 19 '22 at 12:18
0

I am wondering if I could collect only the Persons with minimum loanAmount with just single pass without having to collect other Person in map which I dont need.

In case if you want to avoid creating an intermediate Map you can create a custom collector using static method Collectors.of(), or make use of the three-args version of collect().

Here's an example with three-args collect():

public static void main(String[] args) {

    Collection<Person> minLoanPeople = Stream.of(
        new Person("Alise", BigDecimal.ZERO, null),
        new Person("Bob", BigDecimal.TEN, null),
        new Person("Carol", BigDecimal.ZERO, null)
        )
        .collect(
            ArrayDeque::new,
            (Queue<Person> queue, Person p) -> {
                if (!queue.isEmpty() && queue.peek().loanAmount().compareTo(p.loanAmount()) > 0) queue.clear();
                if (queue.isEmpty() || queue.peek().loanAmount().equals(p.loanAmount())) queue.offer(p);
            },
            (left, right) -> {
                if (left.peek().loanAmount().compareTo(right.peek().loanAmount()) > 0) left.clear();
                if (left.isEmpty() || left.peek().loanAmount().equals(right.peek().loanAmount())) left.addAll(right);
            }
        );
    
    minLoanPeople.forEach(System.out::println);
}

Output:

Person[name=Alise, loanAmount=0, dateOfBirth=null]
Person[name=Carol, loanAmount=0, dateOfBirth=null]
Alexander Ivanchenko
  • 25,667
  • 5
  • 22
  • 46