如何根据属性减去两个对象列表?
How do I subtract two Object Lists based on the attributes?
我有两个 Java 对象列表
让我们说 dataToBeAdded,dataToBeSubtracted
对象属于同一数据类型,具有多个属性
虚拟对象{
属性1,
属性2,
属性3,
属性4
}
我想合并这些列表,但是以有条件的方式
如果 attr1、attr2、attr3 在列表中匹配
减去attr4,使其成为列表的一部分。
如果属性不匹配
元素来自 list1(dataToBeadded) 按原样添加到输出
元素来自 list2(dataToBeSubtracted) 使 attr4 为负数(乘以 -1)
这有点像 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
创建一个链式流并映射减去的每个元素 List
到 attr4
字段已经取反的元素。
然后,您可以使用 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来测试上面的代码:
如果您真的想要性能,则需要重写管道 - 无论发生什么事情都会让你们两个问这个问题?让它成为 2 个地图,使用前 3 个属性作为键(也许制作一个新的 class 来代表这 3 个属性)。
但是,如果管道不能改变,最快的算法是:
- 编写一个比较器来对这些列表进行排序。
- 创建 2 个迭代器(每个迭代器一个)。
- 制作一个输出列表。
- 循环,为两个迭代器创建一个 'current item' 指针。
- 如果current-1低于current-2(或current-2完成),复制current-1并推进iterator-1。
- 如果current-2低于current-1(或者current-1已经完成),翻转c-2的符号,加上那个,然后推进iterator-2。
- 如果current-1和current-2相同,适当更新attr4,然后推进两者。
- 如果两者都完成,return输出列表。
它的代码会多得多,但除了 2 个迭代器和一个比较器之外,它不会生成任何新的瞬态对象。
我有两个 Java 对象列表
让我们说 dataToBeAdded,dataToBeSubtracted
对象属于同一数据类型,具有多个属性
虚拟对象{ 属性1, 属性2, 属性3, 属性4 }
我想合并这些列表,但是以有条件的方式 如果 attr1、attr2、attr3 在列表中匹配
减去attr4,使其成为列表的一部分。
如果属性不匹配
元素来自 list1(dataToBeadded) 按原样添加到输出
元素来自 list2(dataToBeSubtracted) 使 attr4 为负数(乘以 -1)
这有点像 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
创建一个链式流并映射减去的每个元素 List
到 attr4
字段已经取反的元素。
然后,您可以使用 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来测试上面的代码:
如果您真的想要性能,则需要重写管道 - 无论发生什么事情都会让你们两个问这个问题?让它成为 2 个地图,使用前 3 个属性作为键(也许制作一个新的 class 来代表这 3 个属性)。
但是,如果管道不能改变,最快的算法是:
- 编写一个比较器来对这些列表进行排序。
- 创建 2 个迭代器(每个迭代器一个)。
- 制作一个输出列表。
- 循环,为两个迭代器创建一个 'current item' 指针。
- 如果current-1低于current-2(或current-2完成),复制current-1并推进iterator-1。
- 如果current-2低于current-1(或者current-1已经完成),翻转c-2的符号,加上那个,然后推进iterator-2。
- 如果current-1和current-2相同,适当更新attr4,然后推进两者。
- 如果两者都完成,return输出列表。
它的代码会多得多,但除了 2 个迭代器和一个比较器之外,它不会生成任何新的瞬态对象。