6

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);
3
  • 1
    If you add the implementation of your algorithm it might be easier to convert it to streams and lambda n stuff. Commented Aug 20, 2018 at 13:10
  • What is the logic behind seperating datas into a,b,c,d,e ? If you tell us it can be helpful. Commented Aug 20, 2018 at 13:17
  • @JackFlamp I added my implementation in above edit. hope that helps Commented Aug 20, 2018 at 13:27

4 Answers 4

3

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

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

2 Comments

Thanks for quick answer. But, since we are iterating on mapA, it will miss e= [aIds=null, bIds=[21, 101] entry.
aList and bList can have records present in one and absent in other. Also, both of these list hold a lot of data therefore performance is also important here.
2

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.

10 Comments

Thanks for answer. This works perfectly fine and takes lesser time than my implementation.
@Mak be careful when you say "lesser", measuring some algorithm in java is not easy, by any means
@eugene simplest is best.
@Bohemian say that to TimSort, please
@fed you’re right. I was on the edge of lazy initializing the lists, but your idea give me an “in”. Answer updated.
|
0

You could write something like :

Function<List<MyInfoObject>, Map<String, List<Long>>> toLongMap =
      list -> list.stream()
                  .collect(groupingBy(MyInfoObject::getSignature,
                                      mapping(MyInfoObject::getId, toList())));

Map<String, List<Long>> aMap = toLongMap.apply(aList);
Map<String, List<Long>> bMap = toLongMap.apply(bList);

Map<String, BucketOfAandB> finalMap = new HashMap<>();
aMap.forEach((sign, listA) -> {
    finalMap.put(sign, new BucketOfAandB(listA, bMap.get(sign)));
});
bMap.forEach((sign, listB) -> {
    finalMap.putIfAbsent(sign, new BucketOfAandB(null, listB));
});

1 Comment

Thanks for the answer. This implementations works fine. The time taken is almost equal to my implementation.
0

Like you said, first you can create a Map<String, List<Long>> and then build the Map<String, BucketOfAandB>:

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

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

Map<String, List<BucketOfAandB>> result = Stream.concat(idsBySignatureA.entrySet().stream(), idsBySignatureB.entrySet().stream())
    .collect(Collectors.groupingBy(
        Map.Entry::getKey,
        Collectors.mapping(entry -> 
            new BucketOfAandB(
                idsBySignatureA.get(entry.getKey()),
                idsBySignatureB.get(entry.getKey())), 
            Collectors.toList())
    ));

Also feel free to extract the first part into a function for a better readability.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.