如何根据属性减去两个对象列表?

How do I subtract two Object Lists based on the attributes?

我有两个 Java 对象列表

让我们说 dataToBeAdded,dataToBeSubtracted

对象属于同一数据类型,具有多个属性

虚拟对象{ 属性1, 属性2, 属性3, 属性4 }

我想合并这些列表,但是以有条件的方式 如果 attr1、attr2、attr3 在列表中匹配

减去attr4,使其成为列表的一部分。

如果属性不匹配

这有点像 Full Outer Join 类型的操作

我使用 Maps 和 Streams 做了一些事情

        Map<String, DummyObj> dataToBeAddedMap = dataToBeAdded.stream()
                .collect(Collectors.toMap(obj -> obj.attr1() + obj.attr2() + obj.attr3(), item -> item));

        Map<String, CumulativeSalesDataByHour> dataToBeSubtractedMap = dataToBeSubtracted.stream()
                .collect(Collectors.toMap( obj -> obj.attr1() + obj.attr2() + obj.attr3(), item ->
                        new DummyObject(item.attr1(), item.attr2(), item.attr3(), -1 * item.attr4())));

        Map<String, DummyObject> resultantData = Stream.of(dataToBeAddedMap, dataToBeSubtractedMap)
                .flatMap(map -> map.entrySet().stream())
                .collect(Collectors.toMap(
                        Map.Entry::getKey,
                        Map.Entry::getValue,
                        (v1, v2) -> new DummyObject(v1.attr1(),
                                v1.attr2(),
                                v1.attr3(),
                                v1.attr4() + v2.attr4())
                ));

        System.out.println(resultantData.values());

这给了我想要的结果,但是有没有更有效的方法来实现这个?

编辑 1:

添加输入和预期输出

        DummyObject a1 = new DummyObject("uuid1", "abcd", "mer1", 20D);
        DummyObject a2 = new DummyObject("uuid1", "pqrs", "mer1", 25D);
        DummyObject a3 = new DummyObject("uuid2", "xyz", "mer1", 18D);

        List<DummyObject> dataToBeAdded = ImmutableList.of(a1,a2,a3);

        DummyObject d1 = new DummyObject("uuid1", "abcd", "mer1", 5D);
        DummyObject d2 = new DummyObject("uuid1", "pqrs", "mer1", 2D);
        DummyObject d3 = new DummyObject("uuid3", "xyz", "mer2", 10D);

        List<DummyObject> dataToBeSubtracted = ImmutableList.of(d1,d2,d3);

        Desired Output
        [

            DummyObject("uuid1", "abcd", "mer1", 15D); // 20-5
            DummyObject("uuid1", "pqrs", "mer1", 23D); // 25-2
            DummyObject("uuid2", "xyz", "mer1", 18D); 
            DummyObject("uuid3", "xyz", "mer1", -10D); 

        ]    

无需为要添加的元素和要减去的元素创建两个额外的 Map,您可以立即使用这两个 List 创建一个链式流并映射减去的每个元素 Listattr4 字段已经取反的元素。

然后,您可以使用 collect(Collectors.toMap()) 终端操作在单个 Map 中收集所有对象。关键是前 3 个字段的串联,值是对象本身,而冲突情况可以通过创建一个新的 DummyObj 来处理,其中包含您分组的相同 3 个字段和给定的第四个字段由第一个和第二个碰撞值的 attr4 之和(与您在上一个流中所做的类似)。

另一个小改进可能不是使用 + 运算符链接字符串,它为每个连接创建一个新的 String,而是使用 String.format() 方法生成一个String 按(更少的开销)对元素进行分组。

Map<String, DummyObj> mapRes = Stream.concat(dataToBeAdded.stream(), dataToBeSubtracted.stream().map(obj -> {
            obj.setAttr4(-1 * obj.getAttr4());
            return obj;
        }))
        .collect(Collectors.toMap(obj -> String.format("%s%s%s", obj.getAttr1(), obj.getAttr2(), obj.getAttr3()),
                Function.identity(),
                (obj1, obj2) -> new DummyObj(obj1.getAttr1(), obj1.getAttr2(), obj1.getAttr3(), obj1.getAttr4() + obj2.getAttr4())
        ));

这里有一个link来测试上面的代码:

https://ideone.com/K5qfsI

如果您真的想要性能,则需要重写管道 - 无论发生什么事情都会让你们两个问这个问题?让它成为 2 个地图,使用前 3 个属性作为键(也许制作一个新的 class 来代表这 3 个属性)。

但是,如果管道不能改变,最快的算法是:

  1. 编写一个比较器来对这些列表进行排序。
  2. 创建 2 个迭代器(每个迭代器一个)。
  3. 制作一个输出列表。
  4. 循环,为两个迭代器创建一个 'current item' 指针。
  5. 如果current-1低于current-2(或current-2完成),复制current-1并推进iterator-1。
  6. 如果current-2低于current-1(或者current-1已经完成),翻转c-2的符号,加上那个,然后推进iterator-2。
  7. 如果current-1和current-2相同,适当更新attr4,然后推进两者。
  8. 如果两者都完成,return输出列表。

它的代码会多得多,但除了 2 个迭代器和一个比较器之外,它不会生成任何新的瞬态对象。