2

There is ObjectOuter which contains another object called ObjectInner which is having an ID. We need to remove Redundant ObjectOuter having duplicate ObjectInner Ids. (We have another DateFinish object in picture)

public static List<ObjectOuter> removeRedundantObject(List<ObjectOuter> ObjectOuterOriginal){
      if(ObjectOuterOriginal.size() == 1) {
         return ObjectOuterOriginal;
      }
      List<ObjectInner> allObjectInner = ObjectOuterOriginal.stream().map(ObjectOuter::getObjectInner).collect(toList());
      List<Long> allObjectInnerIds = allObjectInner.stream().map(ObjectInner::getObjectInnerId).collect(toList());
      List<ObjectOuter> myFinalObjectOuter = new ArrayList<>();
      if(ObjectOuterOriginal.size() == allObjectInnerIds.stream().distinct().collect(Collectors.toList()).size()){
         return ObjectOuterOriginal;
      }
      Set<Long> duplicateObjectOuter = CommonUtils.getDuplicateNumbers(allObjectInnerIds); //Returns numbers which are duplicate in set
      if(SetUtils.emptyIfNull(duplicateObjectOuter).isEmpty()){
        return ObjectOuterOriginal;
      } else {
        duplicateObjectOuter.forEach((objectInnerId) -> {
              List<ObjectOuter> myOwnObjectOuter = ObjectOuterOriginal.stream().filter(d -> d.getObjectInner().getObjectInnerId().equals(objectInnerId) && d.getDateFinish()==null).collect(Collectors.toList());
              if(ListUtils.emptyIfNull(myOwnObjectOuter).isEmpty()) {
                  LocalDate maxDate = ObjectOuterOriginal.stream().filter(d -> d.getObjectInner().getObjectInnerId().equals(objectInnerId) && d.getDateFinish()!=null).map(u -> u.getDateFinish()).max(LocalDate::compareTo).get();
                  List<ObjectOuter> ownObjectOuter = ObjectOuterOriginal.stream().filter(d -> d.getObjectInner().getObjectInnerId().equals(objectInnerId) && d.getDateFinish()!=null).filter(d -> d.getDateFinish().compareTo(maxDate) == 0).collect(toList());
                  myFinalObjectOuter.addAll(ownObjectOuter);
              } else {
                  myFinalObjectOuter.addAll(myOwnObjectOuter);
              }
        });
        duplicateObjectOuter.forEach((objectInnerId) -> {
            ObjectOuterOriginal.removeIf(d -> d.getObjectInner().getObjectInnerId().compareTo(objectInnerId) == 0);
        });
        ObjectOuterOriginal.addAll(myFinalObjectOuter);
      }
      return ObjectOuterOriginal;
  }

Also we need to use filter on innerObject to select only those ids whose date is either NULL or having max date among duplicate elements; where date is located in outer object.

Above code is executing properly but it is suggested to handle it in more elegant way in Java 8. I can only think of omitting first if statement. but is there any scope of merging statements in Java-8 for above snippet?

fatherazrael
  • 5,511
  • 16
  • 71
  • 155
  • 3
    Simplify your requirement and post a question rather than posting hard to read and time consuming code like this. Then port the solution to yours. – Ravindra Ranwala Jun 07 '18 at 04:20

2 Answers2

3

You could just use the stream’s filter method.

Set<Long> ids = new HashSet<>();

// Note: add returns true if element was not in the set
// so first occurrence will pass through filter,
// subsequent duplicates will be filtered out. 
return ObjectOuterOriginal.stream()
    .filter( o -> ids.add(o.getObjectInner().getObjectInnerId()) )
    .collect( Collections.toList() );

Yes, this is a stateful operation in the filter call, so is probably considered “bad form”, but it gets the job done, is short, simple, fast, and understandable.

AJNeufeld
  • 8,526
  • 1
  • 25
  • 44
  • Thanks. Helpful. Any idea how to use filter in same as we need to put only that inner element in set whose dato is either NULL or having max dato among duplicate elements? – fatherazrael Jun 07 '18 at 04:49
  • Note that bulk operations on collections do not rely on the statelessness of a predicate, so when doing the operation in-pace, `ObjectOuterOriginal.removeIf( o -> ! ids.add(o.getObjectInner().getObjectInnerId()) );`, there is nothing wrong with that… – Holger Jun 07 '18 at 11:08
  • @fatherazrael Sorry, I missed that requirement when reading the text and only skimming the code. To choose which among the “duplicates” to keep you need to collect to a map (based on key), where the selection is done when combining values. Ryan shows how to do this properly in their answer. – AJNeufeld Jun 07 '18 at 14:12
1

If your id's were already unique, you could simply do:

public static void main(String[] args) {
    Outer[] outers = {
            new Outer(new Inner("a")),
            new Outer(new Inner("b")),
            new Outer(new Inner("c")),
            new Outer(new Inner("a")),
            new Outer(new Inner("b")),
            new Outer(new Inner("c")),
    };

    Map<String, Outer> OutersById = Arrays.stream(outers).collect(Collectors.toMap(outer -> outer.inner.id, outer -> outer));

    OutersById.forEach((k,v)->System.out.println(k+", "+v));
}

But this will result in a Duplicate Key Exception.

In order to manage your duplicates, and use your own disambiguation strategy,

Map<String, Outer> OutersById = Arrays.stream(outers).collect(Collectors.toMap(outer -> outer.inner.id, outer -> outer));

Can be changed to

Map<String, Outer> OutersById = Arrays.stream(outers)
                .collect(
                        Collectors.toMap(
                                outer -> outer.inner.id, 
                                outer -> outer,
                                (a, b) -> a.hashCode() > b.hashCode() ? a : b
                                ));

Or whichever other strategy you want, maybe outer has a date on it that can be compared?

Maybe a and b can be joined, into a new object ab?

Update:

public static void main(String[] args) {
        Instant now = Instant.now();
        Instant nxtWeek= now.plus(Duration.ofDays(7));

        Outer[] outers = {
                new Outer(new Inner("a"), null),
                new Outer(new Inner("b"), Date.from(now)),
                new Outer(new Inner("c"), Date.from(nxtWeek)),

                new Outer(new Inner("a"), Date.from(now)),
                new Outer(new Inner("b"), Date.from(nxtWeek)),
                new Outer(new Inner("c"), null),

        };

        Comparator<Outer> outerRanker = Comparator.comparing(Outer::getFinishDate, Comparator.nullsLast(Date::compareTo));

        Map<String, Outer> OutersById = Arrays.stream(outers)
                .collect(
                        Collectors.toMap(
                                outer -> outer.inner.id,
                                outer -> outer,
                                (a, b) -> outerRanker.compare(a,b) > 0 ? a : b
                                ));
        System.out.println("today: "+Date.from(now));
        OutersById.forEach((k,v)->System.out.println(k+", "+v));
    }

Results in

today: Thu Jun 07 15:11:12 ACST 2018
a, Outer{inner=Inner{id='a'}, FinishDate=null}
b, Outer{inner=Inner{id='b'}, FinishDate=Thu Jun 14 15:11:12 ACST 2018}
c, Outer{inner=Inner{id='c'}, FinishDate=null}

Process finished with exit code 0
Ryan Leach
  • 4,262
  • 5
  • 34
  • 71
  • Thanks. The date is in OuterObject.(FinishDate). The conditions are: to chose only that from duplicate ids which has finishDate as null or choose only that from duplicate ids which has highest finish date (if all dates are populated). – fatherazrael Jun 07 '18 at 05:16
  • 1
    I've updated to show the Java 8 way of resolving the priority, using your dates, and sorting order. Note, you could expand this to cover the cases where the date is exactly the same, or multiple nulls, at the moment it will just pick one. – Ryan Leach Jun 07 '18 at 05:43
  • Why is `tomorrow` 7 days from `now`? ;-) – AJNeufeld Jun 07 '18 at 20:40
  • Originally it was one day, but then I boosted it to a week because of time zones and not wanting to confuse anyone, forgot to change the variable name. – Ryan Leach Jun 08 '18 at 00:55