【问题标题】:Merge two lists of objects into Map having value as different object in java 8将两个对象列表合并到 Map 中,其值为 java 8 中的不同对象
【发布时间】:2019-01-26 14:53:20
【问题描述】:

我有两个相同类型“MyInfoObject”的列表(比如 A 和 B):

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

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

我想创建这两个列表的映射,以便列表 A 的所有 id 和具有相同签名的列表 B 的所有 id 创建一个类型为“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;
  }
 }

所以,我的输出将是Map&lt;String, BucketOfAandB&gt;,其中密钥是签名

例如我的输入是:

    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"));

在这种情况下我的输出将是:

{
    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]],
}

我想使用 java 8 的 Streams 来实现。

我想到的一种方法是:

  1. 从aList 创建Map&lt;String, List&lt;Long&gt;&gt;,比如aBuckets
  2. 迭代 bList 并通过以下方式创建 resultant Map&lt;String, BucketOfAandB&gt;
    • 2a。将具有相同签名的 aBucket 中的列表设置为结果,将其从 aBuckets 中删除
    • 2b。将bList的元素添加到所需的签名桶中
  3. 迭代aBuckets的所有剩余元素并将它们添加到resultant

我想知道使用 Java 8 的 Streams 实现此功能的更好方法。

提前致谢!

编辑: 我尝试使用流,但对实现不太满意。以下是我的逻辑:

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);

【问题讨论】:

  • 如果您添加算法的实现,可能会更容易将其转换为流和 lambda n 的东西。
  • 将数据分成 a,b,c,d,e 背后的逻辑是什么?如果您告诉我们这可能会有所帮助。
  • @JackFlamp 我在上面的编辑中添加了我的实现。希望有帮助

标签: java lambda collections java-8 java-stream


【解决方案1】:

这个怎么样:

    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))));

但这假设listA 中存在的每个键都将存在于listB 中

【讨论】:

  • 感谢您的快速回答。但是,由于我们在 mapA 上进行迭代,它会错过 e= [aIds=null, bIds=[21, 101] 条目。
  • aList 和 bList 可以在一个中存在记录,而在另一个中不存在记录。此外,这两个列表都包含大量数据,因此性能在这里也很重要。
【解决方案2】:

如果您将 getter 添加到 MyInfoObject,并让 BucketOfAandB 延迟初始化其列表(即没有构造函数),如下所示:

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);
    }
}

您只需 3 行即可完成,同时保留您的意图语义:

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()));

如果您使用并行流,synchronize add 方法,这实际上不会增加​​性能损失,因为它只是存储桶上的潜在冲突。

【讨论】:

  • 感谢您的回答。这工作得非常好,并且比我的实现花费更少的时间。
  • @Mak 说“较小”的时候要小心,无论如何,在 java 中测量一些算法并不容易
  • @eugene 最简单的就是最好的。
  • @Bohemian 请告诉TimSort
  • @fed 你是对的。我正处于懒惰初始化列表的边缘,但你的想法给了我一个“in”。答案已更新。
【解决方案3】:

你可以这样写:

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));
});

【讨论】:

  • 感谢您的回答。这个实现工作正常。花费的时间几乎等于我的实现。
【解决方案4】:

如你所说,首先你可以创建一个Map&lt;String, List&lt;Long&gt;&gt;,然后再构建Map&lt;String, BucketOfAandB&gt;:

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())
    ));

也可以随意将第一部分提取到一个函数中以获得更好的可读性。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2018-04-21
    • 2019-09-08
    • 1970-01-01
    • 2021-09-10
    • 1970-01-01
    • 1970-01-01
    • 2021-10-29
    • 1970-01-01
    相关资源
    最近更新 更多