使用 Stream IPA 将一对元素映射为一个元素

Mapping pair of elements into one with Stream IPA

所以我想知道以下问题的最佳解决方案是什么:

我在 java 集合 ex

中有一个项目列表(自定义 class)
List<Item> itemList = ... item1,item2,item3 etc

然而,集合中的每个项目在集合中也有对应的逻辑对(因此这对不一定按集合中的索引彼此跟随)

我有一个像

这样的辅助方法
Item calculateCorrectItem(Item item1, Item item2)

哪个可以 return 基于某些业务逻辑的一对中的正确一个(详细信息不相关)

我想替换集合中的一个项目及其对用上述方法的结果 - 这样每个集合中一对元素中的 2 个元素被替换为基于这两个计算出的元素。

一些细节: 我们可以假设每个元素只有一对。
每件物品都有配对的 ID,如 属性,如

  public class Item {
    private String id;
    private String pairId;

    the equal method is true when the ID of two items are the same.
    ...getters,setters
  }

此外,我要过滤的集合中的引用也存在于全局缓存中,可以从中轻松检索每个项目,例如

globalCache.getItemById(String id)

因此,如果对的 ID 已知,则可以轻松检索到实际的对参考。

什么是优雅的解决方案(也许通过使用 Stream IPA)?最后,唯一的期望是集合包含每对中的一个项目,顺序无关紧要

对于流,您必须使用索引访问来执行此操作:

List<Item> calculated =
    IntStream.range(0, itemList.size() / 2)
        .mapToObj(i -> calculateCorrectItem(itemList.get(2*i+0), itemList.get(2*i+1))
        .collect(toList());

如果您想根据 ID 合并项目,可以按 ID 对项目进行分组:

itemList.stream()
    .collect(groupingBy(Item::getId))  // Yields a Map<IdType, List<Item>>
    .values()  // Yields a Collection<List<Item>>, where each List<Item> contains items with the same id. 
    .stream()
    .map(is -> /* invoke calculateCorrectItem, handling 1, 2 or more items in the list */)
    .collect(...)

我假设无论参数的顺序如何,方法 calculateCorrectItem(Item item1, Item item2) 都会产生相同的结果,并且必须从结果列表中删除重复的结果。

List<Item> items = ... ; // obtain the items

Map<String, Item> itemById = items.stream()
       .collect(Collectors.toMap(Item::getId, // relies on uniquness of Id
                                 Function.identity()));

// set is used to alliminate duplicates since their order is not important
Set<Item> itemSet = items.stream()
        .map(item-> pairById.containsKey(item.getPairId()) ? item : // if pair isn't present return the same item, othewise merge them
                        calculateCorrectItem(item, pairById.get(item.getPairId())))
        .collect(Collectors.toSet());

List<Item> result = new ArrayList<>(itemSet);

这是使用映射执行可变缩减的另一种方法(如果保留源列表的 ID 对顺序不重要,则可以使用哈希映射):

Collection<Item> correctItems1 = itemList.stream().collect(
    LinkedHashMap<String, Item>::new, 
    (map, item) -> map.merge(item.getPairId(), item, this::calculateCorrectItem),
    Map::putAll)
  .values();

List<Item> result = new ArrayList<>(correctItems1);

这是另一种使用带有合并功能的 Collectors.toMap 的方法:

  • 首先,为演示创建一个记录并用一些数据初始化一个列表
record Item(String getId, int getValue) {
}
    

Random r = new Random();
List<Item> items = r.ints(10, 1, 5)
        .mapToObj(id -> new Item(id+"", r.nextInt(100) + 1))
        .toList();
System.out.println("The raw data");

items.forEach(System.out::println);
System.out.println();
  • 现在播放列表
  • 使用 toMap 的第三个参数“合并”新项目。
Collection<Item> collection = items.stream()
                .collect(Collectors.toMap(Item::getId, item->item,
                        (item1, item2) -> calculateCorrectItem(item1,
                                item2)))
                .values();
System.out.println("The new list of combined items");
collection.forEach(System.out::println);

版画

The raw data
Item[getId=1, getValue=14]
Item[getId=4, getValue=42]
Item[getId=4, getValue=19]
Item[getId=2, getValue=16]
Item[getId=4, getValue=20]
Item[getId=3, getValue=57]
Item[getId=3, getValue=47]
Item[getId=3, getValue=22]
Item[getId=1, getValue=3]
Item[getId=4, getValue=73]

The new list of combined items
Item[getId=1, getValue=17]
Item[getId=2, getValue=16]
Item[getId=3, getValue=126]
Item[getId=4, getValue=154]

上面使用的方法。它只是对值和 returns 一个新的 Item 实例求和。

public static Item calculateCorrectItem(Item one, Item two) {
        return new Item(one.getId(), one.getValue() + two.getValue());
}

一个简单的 non-stream 解决方案打印出与以前相同的结果。

Map<String, Item> result = new HashMap<>();
for (Item item : items) {
    result.compute(item.getId(),
            (k, v) -> v == null ? item : new Item(v.getId(),
                    v.getValue() + item.getValue()));
}

result.values().forEach(System.out::println);