使用另一个流中的值过滤流

3

我很难想出一个适用于我的问题的通用解决方案。假设我有一个复杂的数据结构D。我想要找到满足给定过滤器值F的谓词P的D中的所有元素,并将结果存储在一个堆栈中。对于正谓词和负谓词,我提出了两种不同的解决方案。

List<Integer> sample = Arrays.asList(0,1,2,3,4,5,6,7,8,9);
List<String> values = Arrays.asList("4","5","6");

BiPredicate<Integer, String> predicate = (d,f) -> d.equals(Integer.valueOf(f));
Function<Integer, Integer> converter = Function.identity();

Collection<Integer> filtered = sample.parallelStream()
                                     .filter(d -> values.parallelStream()
                                                        .anyMatch(f -> predicate.test(d, f)))
                                     .map(converter::apply)
                                     .collect(Collectors.toCollection(Stack::new));

问题: 上述方法只适用于负谓词。在上面的例子中,当谓词为负时,结果为[4, 5, 6]。但是,如果我将谓词更改为!d.equals(Integer.valueOf(f)),则结果变为[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]。为了解决负谓词,我必须将过滤器更改为:
.filter(d -> values.parallelStream()
                   .distinct()
                   .allMatch(f -> predicate.test(d, f))

但这样做会破坏正面的谓词。问题有点复杂,因为示例中包含具有不同类型多个属性的对象。BiPredicate用于定义满足使用值作为过滤器的条件的筛选。上面的例子已经简化了,但正确地显示了我现在面临的问题...

任何人都可以给我一个提示,我该如何编写这个lambda适用于两种情况?

@澄清: 看起来我没有足够清楚地表达一个重要观点,让我详细说明一下。在我给出的例子中,我有一个简单的整数集合,并不意味着我面临这个问题。让我们深入探讨一下...

class SampleDataStructure {
    PropertyType_0 property_0;
    PropertyType_1 property_1;
    ...
    PropertyType_N property_n;

    // getters defined.
}

Collection<SampleDataStructure> sample = ...; // Let's assume it has been initialized.

现在,让我们选取SampleDataStructure的任意属性(PropertyType_I property_i)作为筛选集合的关键。我还有另一个类型为PropertyType_I的集合:

Collection<PropertyType_I> values = ...; //A set of values that will be used by the predicate.

我也有一个谓词,为了简单起见:
BiPredicate<SampleDataStructure, PropertyType_I> predicateA = (data, value) -> data.getPropertyI().equals(value);
BiPredicate<SampleDataStructure, PropertyType_I> predicateB = (data, value) -> !data.getPropertyI().equals(value);

我希望找到所有与谓词匹配的SampleDataStructures。它可以是predicateA或predicateB。我不知道它会是什么,所以请发挥想象力。我提供了这两个选项,因为我的方法存在问题(请参见我的帖子的第一部分)。然后,我想在这些SampleDataStructures上使用给定的转换器,并将它们映射到完全不同的内容,并将结果作为集合(目前为Stack)返回。
例如:
sample = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
values = [4, 5, 6]

Predicate_1 = (s, v) -> s == v       Result = [4, 5, 6]
Predicate_2 = (s, v) -> s != v       Result = [0, 1, 2, 3, 7, 8, 9]

values = []

Predicate_1 = (s, v) -> s == v       Result = []
Predicate_2 = (s, v) -> s != v       Result = []

values = [99]

Predicate_1 = (s, v) -> s == v       Result = []
Predicate_2 = (s, v) -> s != v       Result = []

所有这些意味着,那些假设样本或值只是简单类型的提议解决方案都是错误的。它们都可以是任意复杂的数据结构,而BiPredicate则告诉样本数据应该如何针对这些值进行过滤。希望这能澄清问题。

下面是另一个例子:

class Person {
    private long id;
    String name;
    public Person(long id, String name) { this.id = id; this.name = name; }
    public long getId() { return id; }
    public Strin getName() { return name; }
}

Collection<Person> persons = Arrays.asList(new Person(1, "Jane"), new Person(2, "Doe"), new Person(3, "Jane Doe"), new Person(4, "John"), new Person(5, "whatever John"), ...);
BiPredicate<Person, String> predicate = (p, f) -> p.getName().matches(f);
Function<Person, String> personToName = Person::getName;
List<String> selectors = Arrays.asList("^Jane$", "John$");

作为结果,我希望得到 [ Jane,John,无论是John ]

然而,我提供的方法存在一个问题,即如果我有:

BiPredicate<Person, String> predicate = (p, f) -> !p.getName().matches(f);

我没有得到 [Jane Doe, 和 ... 部分]。我得到了每一项。我没有两个谓词,我展示了否定的一个,因为那个不起作用。


“负谓词”不应该是!d.equals(Integer.valueOf(f));吗? - Kayaman
是的,抱歉那是我的打字错误。 - Display name
我猜你需要澄清,你最终想要什么?你想将预测结果的正负参数分别放入不同的堆栈中吗?使用单个流吗?如果这是你的目的,你需要使用分组(或“partitioningBy”)而不是过滤器*。 - Ömer Erden
不,我只有一个谓词。问题在于当我使用“否定”谓词时,我的解决方案会出现错误,而我找不到原因。我已经添加了一些样例来进行澄清。 - Display name
@Displayname 原因显而易见,您的负面预测至少返回一次是正确的。如何实现?您的第一个集合至少有一个元素与第二个集合中的任何值都不相等,将当前元素视为(int)1来考虑,然后深入筛选检查是否为负面预测。第二个集合有"1"作为"任意"元素吗?没有!因此,您的负面预测将返回 true,并且将获得任何匹配。然后过滤器将无法工作,因为您所有的任何匹配调用都将返回true。它只对两个集合具有单个且相同的元素才有效。 - Ömer Erden
5个回答

2

布尔补集

anyMatch(s -> predicate.test(p, s))

根据德摩根定律(参见德摩根定律),是这样的。
allMatch(s -> !predicate.test(p, s))

但是(与你在帖子中似乎想的不同)。
anyMatch(s -> !predicate.test(p, s))

适用于您的情况:
Collection<Person> filtered = persons
            .parallelStream()
            .filter(p -> selectors.parallelStream().anyMatch(s -> predicate.test(p, s)))
            .collect(Collectors.toCollection(Stack::new));

将会提供一个人员集合。并且。
Collection<Person> filtered = persons
            .parallelStream()
            .filter(p -> selectors.parallelStream().allMatch(s -> !predicate.test(p, s)))
            .collect(Collectors.toCollection(Stack::new));

将提供正确的互补人员收集。

基本上,在这种情况下,IN和NOT_IN是两个完全不同的算法,我不能为它们两个编写一个单一的语句。 - Display name

1
您可以使用 List.contains 更轻松地完成此操作:
Collection<Integer> filtered = sample.parallelStream()
                                     .filter(f -> values.contains(String.valueOf(f)))
                                     .collect(Collectors.toCollection(Stack::new));

给出结果[4, 5, 6]。并且

Collection<Integer> filtered = sample.parallelStream()
                                     .filter(f -> !values.contains(String.valueOf(f)))
                                     .collect(Collectors.toCollection(Stack::new));

输出结果为[0, 1, 2, 3, 7, 8, 9]。我有遗漏什么吗?


但他没有使用“Lists”,而是使用“Streams”。 - Kayaman
我说过样本包含一个!Complex!数据结构。另外,过滤器可能包含另一个复杂的数据结构。使用List.contains时,我们假设这些值完全相同。那么如果我有一个正则表达式,并在BiPredicate中使用它进行匹配呢?这种解决方案不够灵活。Predicate就是为了解决这个问题,我只是不知道为什么负面谓词会导致我的解决方案停止工作。 - Display name

0
List<MyModel> MyModelAccountList = MyModelAccountList1.stream().filter(mymodel -> customerAcctList.stream().anyMatch(customerAccounts -> doesCustomerAccountMatch(customerAccounts, mymodel))
                ).collect(Collectors.toList());

1
虽然这可能回答了这个问题,但最好在此处包含答案的基本部分并提供详细信息以供接受。请提供更多细节。 - Tasnuva Tavasum oshin

0

这是我认为的内容

public static void main(String[] args) {
    List<Integer> sample = Arrays.asList(0, 1, 2, 3, 4, 5, 6, 7, 8, 9);
    List<String> values = Arrays.asList("4", "5", "6");

    BiPredicate<Integer, String> predicate = (d, f) -> {
        return d.equals(Integer.valueOf(f));
    };

    Function<Integer, Integer> converter = Function.identity();

    Map<Integer, Boolean> filtered =
            sample.parallelStream()
                    .collect(Collectors.toMap(intVal -> converter.apply(intVal), intVal -> values.parallelStream().noneMatch(stringVal -> predicate.test(intVal, stringVal))));

    System.out.println("filtered:");
    filtered.forEach((k,v) -> {
        System.out.println("positive:" + k + " val: " + v);
    });

}

结果:

positive:0 val: true
positive:1 val: true
positive:2 val: true
positive:3 val: true
positive:4 val: false
positive:5 val: false
positive:6 val: false
positive:7 val: true
positive:8 val: true
positive:9 val: true

问题在于找到一段能够正确处理两种情况的代码。 - Kayaman
谢谢,我编辑了我的回答,但在问题中看不到。 - utkusonmez
看起来很有前途,但是在集合器中出现了堆栈。我不想要每个元素,我只需要筛选过的元素。恐怕我不能真正用这种方法满足我的需求。 - Display name
你可以转换任何你想要的!你的堆栈里有什么?如何复现一个项目处于正面情况或负面情况? - utkusonmez

0

关于Thomas Fritsch的答案,你可以通过改变Predicate来在一行代码中实现它。你不再需要BiPredicate了。

Predicate<Integer> predicate = (f) -> !values.contains(String.valueOf(f));
Predicate<Integer> predicatePositive = (f) -> values.contains(String.valueOf(f));
Function<Integer, Integer> converter = Function.identity();

Collection<Integer> filtered = sample.parallelStream()
                                    .filter(d -> values.parallelStream()
                                    .anyMatch(f -> predicate.test(d)))
                                    .map(converter)
                                    .collect(Collectors.toCollection(Stack::new));

如果数据结构不那么复杂那就好了。似乎没有强调到位,我会更新我的帖子。我只是通过在列表中使用整数来简化了问题。 - Display name

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接