-1

I have a stream over a simple Java data class like:

class Developer{
    private Long id;
    private String name;
    private Integer codePost;
    private Integer codeLevel;
}

I would like to apply this filter to my stream :

  • if 2 dev has the same codePost with different codeExperience keep the dev with codeLevel = 5

  • keep all devs if Developers has the same codePost with the same codeLevel

Example

ID name codePost codeExperience
1 Alan stonly 30 4
2 Peter Zola 20 4
3 Camilia Frim 30 5
4 Antonio Alcant 40 4

or in java

Developer dev1 = new Developer (1,"Alan stonly",30,4);
Developer dev2 = new Developer (2,"Peter Zola",20,4);
Developer dev3 = new Developer (3,"Camilia Frim ",30,5);
Developer dev4 = new Developer (4,"Antonio Alcant",40,4);

Stream<Developer> Developers = Stream.of(dev1, dev2, dev3 , dev4);
bloudr
  • 79
  • 7
  • Streams are not very well-suited to problems that need to take the state of previous entries into account. You could collect your entries with the 3 args version of [`Collectors.toMap()`](https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/util/stream/Collectors.html#toMap(java.util.function.Function,java.util.function.Function,java.util.function.BinaryOperator)), with `codePost` as key and 'merging' the values to keep the one you want. – Hulk Oct 06 '21 at 09:03
  • 1
    `developers.collect(Collectors.toMap(Developer::getCodePost, Function.identity(),BinaryOperator.maxBy(Comparator.comparing(Developer::getCodeLevel)))).values();` – Hadi J Oct 06 '21 at 09:07
  • @Hulk yes that seems good, but how I could filter by the codeExperience in my case? – bloudr Oct 06 '21 at 09:24
  • Well, forget it - the approach shown in [the linked answer](https://stackoverflow.com/a/27872852/2513200) (stateful `Predicate` in `Stream.filter()`) is not applicatble to your case, because you cannot take back what you already emitted after the fact. I've retracted my duplicate-close vote. I'd just use a Map - either somthing along the lines of [the comment by Hadi](https://stackoverflow.com/questions/69462527/filter-stream-distinct?noredirect=1#comment122776344_69462527) or simply with a loop instead of a Stream. – Hulk Oct 06 '21 at 09:31
  • _keep all devs if Developers has the same codePost with the same codeLevel_ if there are 4 devs, two have codeLevel = 2, and the other two have codeLevel = 3, which devs should be kept: the ones with the lowest codeLevel, those with the max codeLevel, all of them, or none of them? – Nowhere Man Oct 06 '21 at 13:21

1 Answers1

0

As mentioned in the comments, Collectors.toMap should be used here with the merge function (and optionally a map supplier, e.g. LinkedHashMap::new to keep insertion order):

Stream.of(dev1, dev2, dev3, dev4)
    .collect(Collectors.toMap(
        Developer::getCodePost,
        dev -> dev,
        (d1, d2) -> Stream.of(d1, d2)
                          .filter(d -> d.getCodeLevel() == 5)
                          .findFirst()
                          .orElse(d1),
        LinkedHashMap::new // keep insertion order
    ))
    .values()
    .forEach(System.out::println);

The merge function may be implemented with ternary operator too:

(d1, d2) -> d1.getCodeLevel() == 5 ? d1 : d2.codeLevel() == 5 ? d2 : d1

Output:

Developer(id=3, name=Camilia Frim , codePost=30, codeLevel=5)
Developer(id=2, name=Peter Zola, codePost=20, codeLevel=4)
Developer(id=4, name=Antonio Alcant, codePost=40, codeLevel=4)

If the output needs to be sorted in another order, values() should be sorted as values().stream().sorted(DeveloperComparator) with a custom developer comparator, e.g. Comparator.comparingLong(Developer::getId) or Comparator.comparing(Developer::getName) etc.


Update

As the devs sharing the same codeLevel should NOT be filtered out, the following (a bit clumsy) solution is possible on the basis of Collectors.collectingAndThen and Collectors.groupingBy:

  1. input list is grouped into a map of codePost to the list of developers
  2. then the List<Developer> values in the map are filtered to keep the devs with max codeLevel
// added two more devs
Developer dev5 = new Developer (5L,"Donkey Hot",40,3);
Developer dev6 = new Developer (6L,"Miguel Servantes",40,4);

Stream.of(dev1, dev2, dev3, dev4, dev5, dev6)
      .collect(Collectors.collectingAndThen(Collectors.groupingBy(
              Developer::getCodePost
      ), map -> {
          map.values()
             .stream()
             .filter(devs -> devs.size() > 1)
             .forEach(devs -> {
                 int maxLevel = devs.stream()
                     .mapToInt(Developer::getCodeLevel)
                     .max().orElse(5);
                 devs.removeIf(x -> x.getCodeLevel() != maxLevel);
             });
          return map;
      }))
      .values()
      .stream()
      .flatMap(List::stream)
      .sorted(Comparator.comparingLong(Developer::getId))
      .forEach(System.out::println);

Output:

Developer(id=2, name=Peter Zola, codePost=20, codeLevel=4)
Developer(id=3, name=Camilia Frim , codePost=30, codeLevel=5)
Developer(id=4, name=Antonio Alcant, codePost=40, codeLevel=4)
Developer(id=6, name=Miguel Servantes, codePost=40, codeLevel=4)
Nowhere Man
  • 19,170
  • 9
  • 17
  • 42
  • Thanks for you reply but I need to keep all devloper if Developers has the same codePost with the same codeLevel – bloudr Oct 06 '21 at 11:36
  • Why did not you post this requirement in the original post? Are there other cases like two or more devs have the came codePost but different codeLevel and nobody has codeLevel == 5? – Nowhere Man Oct 06 '21 at 12:46
  • i thought this condition should not be added to the filter , but after testing I have realized it should be . sorry I added that to original post . – bloudr Oct 06 '21 at 13:03