使用Java 8 Predicate查找“最”正确的值

3

感谢您查看我的问题!

我在使用多个特定顺序应用的Stream时遇到了一些困难。

为了举例说明,请考虑以下IntPredicates:

        IntPredicate divisibleByThree = i -> i % 3 == 0;
        IntPredicate divisibleByFour = i -> i % 4 == 0;
        IntPredicate divisibleByFive = i -> i % 5 == 0;
        IntPredicate divisibleByThreeAndFour = divisibleByThree.and(divisibleByFour);
        IntPredicate divisibleByThreeAndFive = divisibleByThree.and(divisibleByFive);
        IntPredicate divisibleByThreeAndFiveAndFour = divisibleByThreeAndFour.and(divisibleByFive);
        //....arbitrary Number of predicates.

第一部分

我把我的问题转化为一个类似于“FizzBuzz”的版本,尝试通过按特定顺序将谓词应用于流来找到正确的答案。就像这样:

    IntStream.range(1, 100).forEach(i -> {
        //Order matters here!
        if(divisibleByThreeAndFiveAndFour.test(i)){
            System.out.println("Three and four and five");
        } else if(divisibleByThreeAndFour.test(i)){
            System.out.println("Three and four");
        } else if(divisibleByThreeAndFive.test(i)){
            System.out.println("Three and four");
        } else if(divisibleByFive.test(i)){
            System.out.println("Five");
        }
        //etc, etc.
    });

我觉得这段代码不太美观,有没有更好的方法来实现它?

第二部分

如果我真的需要应用谓词来查看流中是否存在适当的值,并计算相关的值以返回(在这种情况下,是要打印的字符串),那该怎么办呢?这会是什么样子?

一种提出的天真解决方案:

String bestValueFound = "None found";
if(IntStream.range(1, 100).filter(divisibleByThreeAndFiveAndFour).findFirst().isPresent()){
    bestValueFound = "Three and four and five";
} else if(IntStream.range(1, 100).filter(divisibleByThreeAndFour).findFirst().isPresent()){
    bestValueFound = "Three and four";
}else if(IntStream.range(1, 100).filter(divisibleByThreeAndFive).findFirst().isPresent()){
    bestValueFound = "Three and five";
} else if(IntStream.range(1, 100).filter(divisibleByThreeAndFive).findFirst().isPresent()){
    bestValueFound = "Five";
}
System.out.println(bestValueFound);

这看起来更糟,不仅在美学上如此,而且由于添加了迭代而更加糟糕。

第三部分

使用JavaSlang Match可能以更漂亮、更高效的方式解决这个问题吗?

//Note: Predicates needed to be changed from IntPredicate to Predicate<Integer> for correct compilation.
Function<Integer, String> findString = i -> API.Match(i).of(
        Case(divisibleByThreeAndFiveAndFour, "Three and four and five"),
        Case(divisibleByThreeAndFour, "Three and four"),
        Case(divisibleByThreeAndFive, "Three and five"),
        Case(divisibleByFive, "Fice"),
        Case($(), "None found"));
String bestValueFound =  IntStream.range(1, 100).boxed().map(findString).findFirst().orElseThrow(() -> new RuntimeException("Something went wrong?"));
System.out.println(bestValueFound);

显而易见的问题在于“.findFirst()”,在这种情况下是整数1,使整个表达式评估为“未找到”,然后终止。
我想要的是基本上抓取与我的匹配中第一个谓词匹配的任何内容,并使用该值(如果存在),仅在第一个没有匹配项的情况下给我任何与第二个Case匹配的内容,以此类推,仅在流中没有任何谓词匹配任何值时才给我默认值(“未找到”)。
一定有更好的方法来做这件事吧?或者我只是在浪费时间尝试做一些最好留给更传统的命令式风格的事情?
感谢您阅读我的问题!

1
请只提出一个问题。你有三个完全不同的问题。 - 4castle
1
在第二部分中,您应该使用anyMatch - 4castle
1个回答

8
你可以创建一个类来封装谓词及其名称:
class NamedPredicate {
    final String name;
    final IntPredicate predicate;

    NamedPredicate(String name, IntPredicate predicate) {
        this.name = name;
        this.predicate = predicate;
    }

    NamedPredicate and(NamedPredicate other) {
        return new NamedPredicate(this.name + " and " + other.name,
                this.predicate.and(other.predicate));
    }
}
and() 方法允许我们像最初编写的那样组合它们:
NamedPredicate divisibleByThree = new NamedPredicate("three", i -> i % 3 == 0);
NamedPredicate divisibleByFour = new NamedPredicate("four", i -> i % 4 == 0);
NamedPredicate divisibleByFive = new NamedPredicate("five", i -> i % 5 == 0);
NamedPredicate divisibleByThreeAndFour = divisibleByThree.and(divisibleByFour);
NamedPredicate divisibleByThreeAndFive = divisibleByThree.and(divisibleByFive);
NamedPredicate divisibleByThreeAndFiveAndFour = divisibleByThreeAndFour.and(divisibleByFive);

现在我们可以按降序流式传递它们,并打印每个的第一个匹配谓词的名称。
IntStream.range(1, 100)
        .mapToObj(i -> Stream.of(
                    divisibleByThreeAndFiveAndFour,
                    divisibleByThreeAndFour,
                    divisibleByThreeAndFive,
                    divisibleByFive,
                    divisibleByFour,
                    divisibleByThree)
                .filter(p -> p.predicate.test(i))
                .findFirst()
                .map(p -> p.name)
                .orElse("none"))
        .forEach(System.out::println);

1
@PatrickParker 好的,但你可以采取更明确的方法:NamedPredicate and(NamedPredicate other, String newName) { return new NamedPredicate(newName, this.predicate.and(other.predicate)); } - shmosel
你为什么没有考虑到可被4和5整除的情况? - sara
1
@sara,我只是复制了原帖中的列表。 - shmosel
@shmosel 谢谢你!这太棒了!如果你没有指出这个解决方案,我可能会尝试将它们枚举到元组中并对它们进行排序! - Raudbjorn
1
@Patrick Parker:我认为完全没有理由考虑i18n对这个操作的影响。你可以把“name”替换成“resourceBundleKey”,逻辑不会改变。或者你可以让NamedPredicate在构造函数中进行资源束查找,这样你就可以用“localizedName”替换“name”。或者使用资源注入...等等。 - Holger
显示剩余3条评论

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