Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Merge two lists of objects into Map having value as different object in java 8

I have two lists (say A and B) of same type "MyInfoObject" such that :

public class MyInfoObject {
  private Long id;
  private String signature;

  public MyInfoObject(Long id, String signature) {
      super();
      this.id = id;
      this.signature = signature;
  }
}

I want to create a Map of these two lists such that all ids of list A and all ids of list B having same signature creates a bucket of type "BucketOfAandB" :

public class BucketOfAandB {
  private List<Long> aIds ;
  private List<Long> bIds ;

  public BucketOfAandB(List<Long> aIds, List<Long> bIds) {
    super();
    this.aIds = aIds;
    this.bIds = bIds;
  }
 }

So, my output will be Map<String, BucketOfAandB>, where key is signature

Eg my input is :

    List<MyInfoObject> aList = new ArrayList<>();
    aList.add(new MyInfoObject(1l, "a"));
    aList.add(new MyInfoObject(2l, "d"));
    aList.add(new MyInfoObject(3l, "b"));
    aList.add(new MyInfoObject(4l, "a"));
    aList.add(new MyInfoObject(5l, "a"));
    aList.add(new MyInfoObject(6l, "c"));
    aList.add(new MyInfoObject(7l, "a"));
    aList.add(new MyInfoObject(8l, "c"));
    aList.add(new MyInfoObject(9l, "b"));
    aList.add(new MyInfoObject(10l, "d"));

    List<MyInfoObject> bList = new ArrayList<>();
    bList.add(new MyInfoObject(11l, "a"));
    bList.add(new MyInfoObject(21l, "e"));
    bList.add(new MyInfoObject(31l, "b"));
    bList.add(new MyInfoObject(41l, "a"));
    bList.add(new MyInfoObject(51l, "a"));
    bList.add(new MyInfoObject(61l, "c"));
    bList.add(new MyInfoObject(71l, "a"));
    bList.add(new MyInfoObject(81l, "c"));
    bList.add(new MyInfoObject(91l, "b"));
    bList.add(new MyInfoObject(101l, "e"));

My output in this case will be:

{
    a= BucketOfAandB[aIds=[1, 4, 5, 7], bIds=[11, 41, 51, 71]],
    b= BucketOfAandB[aIds=[3, 9], bIds=[31, 91]],
    c= BucketOfAandB[aIds=[6, 8], bIds=[61, 81]],
    d= BucketOfAandB[aIds=[2, 10], bIds=null],
    e= BucketOfAandB[aIds=null, bIds=[21, 101]],
}

I want to do it using Streams of java 8.

One way I figured out was:

  1. create Map<String, List<Long>> from aList, say aBuckets
  2. iterate bList and create resultant Map<String, BucketOfAandB> by
    • 2a. setting List from aBuckets with same signature to resultant, remove it from aBuckets
    • 2b. adding element of bList to required signature bucket
  3. iterate all remaining elements of aBuckets and add them to resultant

I want to know a better way to implement this using Streams of Java 8.

Thanks in advance!

EDIT: I tried using stream but not very happy with implementation. Following is my logic:

Map<String, BucketOfAandB> resultmap  = new HashMap<>();

    // get ids from aList grouped by signature
    Map<String, List<Long>> aBuckets = aList.stream().collect(Collectors.groupingBy(MyInfoObject::getSignature,
            Collectors.mapping(MyInfoObject::getId, Collectors.toList())));

    // iterate bList and add it to bucket of its signature
    bList.forEach(reviewInfo -> {
        BucketOfAandB bucket = resultmap.get(reviewInfo.getSignature());

        if(null ==  bucket) {
            bucket = new BucketOfAandB();
            resultmap.put(reviewInfo.getSignature(), bucket);

            List<Long> sourceReviewBucket =  aBuckets.remove(reviewInfo.getSignature());
            if(null !=sourceReviewBucket) {
                bucket.setaIds(sourceReviewBucket);
            }
        }
        bucket.addToB(reviewInfo.getId());
    });

    Map<String, BucketOfAandB> result = aBuckets.entrySet().stream()
            .collect(Collectors.toMap(Map.Entry::getKey, e -> new BucketOfAandB(e.getValue(), null)));

    resultmap.putAll(result);
like image 530
Mak Avatar asked Aug 20 '18 13:08

Mak


2 Answers

If you add getters to MyInfoObject, and have BucketOfAandB lazy initialize its lists (ie no constructor) like this:

public class BucketOfAandB {
    private List<Long> aIds;
    private List<Long> bIds;
    public void addAId(Long id) {
        if (aIds == null) {
            aIds = new ArrayList<>();
        }
        aIds.add(id);
    }
    public void addBId(Long id) {
        if (bIds == null) {
            bIds = new ArrayList<>();
        }
        bIds.add(id);
    }
}

you can do it in just 3 lines while retaining the semantics of your intention:

Map<String, BucketOfAandB> map = new HashMap<>();
aList.forEach(o -> map.computeIfAbsent(o.getSignature(), s -> new BucketOfAandB())
  .addAId(o.getId()));
bList.forEach(o -> map.computeIfAbsent(o.getSignature(), s -> new BucketOfAandB())
  .addBId(o.getId()));

If you’re using parallel streams, synchronize the add methods, which will add practically no performance hit since it’s only a potential collision on the bucket.

like image 21
Bohemian Avatar answered Oct 01 '22 13:10

Bohemian


How about this:

    Map<String, List<Long>> mapA = aList.stream()
            .collect(Collectors.groupingBy(
                    MyInfoObject::getSignature,
                    Collectors.mapping(MyInfoObject::getId, Collectors.toList())));

    Map<String, List<Long>> mapB = bList.stream()
            .collect(Collectors.groupingBy(
                    MyInfoObject::getSignature,
                    Collectors.mapping(MyInfoObject::getId, Collectors.toList())));

    Map<String, BucketOfAandB> overAll = new HashMap<>();

    Set<String> allKeys = new HashSet<>();
    allKeys.addAll(mapA.keySet());
    allKeys.addAll(mapB.keySet());

    allKeys.forEach(x -> overAll.put(x, new BucketOfAandB(mapA.get(x), mapB.get(x))));

But this assumes that each key present in listA will be present in listB

like image 127
Eugene Avatar answered Oct 01 '22 14:10

Eugene