Java stream collect 检查结果是否包含元素

Java stream collect check if result would contain element

因为我找不到与此相关的任何内容,我想知道流是否允许这样做。

my answer 另一个问题中,我有以下代码将元素添加到结果列表,前提是结果列表尚未包含它:

List<Entry<List<Integer>, Integer>> list = new ArrayList<>(diffMap.entrySet());
list.sort(Entry.comparingByValue());
List<List<Integer>> resultList = new ArrayList<>();
for (Entry<List<Integer>, Integer> entry2 : list) {
    if (!checkResultContainsElement(resultList, entry2.getKey()))
        resultList.add(entry2.getKey());
}

checkResultContainsElement方法:

private static boolean checkResultContainsElement(List<List<Integer>> resultList, List<Integer> key) {
    List<Integer> vals = resultList.stream().flatMap(e -> e.stream().map(e2 -> e2))
            .collect(Collectors.toList());
    return key.stream().map(e -> e).anyMatch(e -> vals.contains(e));
}

现在我想知道,如果这个 for 循环:

for (Entry<List<Integer>, Integer> entry2 : list) {
    if (!checkResultContainsElement(resultList, entry2.getKey()))
        resultList.add(entry2.getKey());
}

可以使用流来实现。我认为 .filter() 方法行不通,因为它会从 List<Entry<List<Integer>, Integer>> list 中删除数据,而我什至不知道是否应该考虑某个元素。我想自定义收集器可以工作,但我也不知道如何实现一个,因为结果会随着每个新添加的元素不断变化。

我正在寻找这样的东西(如果其他东西更好,可能会有所不同):

list.stream().sorted(Entry.comparingByValue()).collect(???);

其中 ??? 将过滤数据并将其 return 作为列表。


一个结果列表的值可能不包含在另一个结果列表中。所以这些列表是有效的:

[1, 2, 3, 4]
[5, 6, 7, 8]
[12, 12, 12, 12]

但其中只有第一个有效:

[1, 2, 3, 4] <-- valid
[5, 3, 7, 8] <-- invalid: 3 already exists
[12, 12, 2, 12] <-- invalid: 2 already exists

可能是这样的:-

 list.stream().
sorted(Entry.comparingByValue()).
collect(ArrayList<List<Foo>>::new,(x,y)->!checkResultContainsElement(x, y.getKey()),(x,y)->x.add(y.getKey()));

如果我们暂时搁置实施是否 stream-based 的细节,可以改进现有的如何检查传入列表值的唯一性的实施。

我们可以通过维护 Set 以前遇到的值来显着提高性能。

即添加到结果列表中的每个 list 中的 values 将存储在 set 中。为了确保每个传入的 list 的唯一性,将根据 set.

检查其值

由于流管道的操作应该是无状态的,收集器也不应该保持状态(即更改应该只在其可变容器内发生)。我们可以通过定义一个容器来解决这个问题,该容器将包含 Foo 列表的结果列表和一组 foo-values.

我已将此容器实现为 Java 16 条记录:

public record FooContainer(Set<Integer> fooValues, List<List<Foo>> foosList) {
    public void tryAdd(List<Foo> foos) {
        if (!hasValue(foos)) {
            foos.forEach(foo -> fooValues.add(foo.getValue()));
            foosList.add(foos);
        }
    }
    
    public boolean hasValue(List<Foo> foos) {
        return foos.stream().map(Foo::getValue).anyMatch(fooValues::contains);
    }
}

上面显示的记录将用作使用 Colloctors.of() 创建的自定义收集器的可变容器。 Collector 的 accumulator 使用容器定义的 tryAdd() 方法。 finisher 从容器中提取 结果列表

注意 此操作不可并行化,因此收集器的 combiner 抛出 AssertionError.

public static void main(String[] args) {
    Map<List<Foo>, Integer> diffMap =
        Map.of(List.of(new Foo(1), new Foo(2), new Foo(3)), 1,
               List.of(new Foo(1), new Foo(4), new Foo(5)), 2,
               List.of(new Foo(7), new Foo(8), new Foo(9)), 3);
    
    List<List<Foo>> result = diffMap.entrySet().stream()
        .sorted(Map.Entry.comparingByValue())
        .map(Map.Entry::getKey)
        .collect(Collector.of(
            () -> new FooContainer(new HashSet<>(), new ArrayList<>()),
            FooContainer::tryAdd,
            (left, right) -> {throw new AssertionError("The operation isn't parallelizable");},
            FooContainer::foosList
        ));

    System.out.println(result);
}

输出:

[[Foo{1}, Foo{2}, Foo{3}], [Foo{7}, Foo{8}, Foo{9}]]