【问题标题】:Finding the "most" correct value using Java 8 Predicate [closed]使用 Java 8 Predicate 找到“最”正确的值 [关闭]
【发布时间】:2017-07-18 15:59:14
【问题描述】:

感谢您查看我的问题!

我在使用 Streams 时遇到了一些麻烦,其中多个谓词按特定顺序应用。

为了举例,请考虑以下 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.

第 1 部分

我已将我遇到的问题转换为“FizzBu​​zz”式的版本,试图通过以特定顺序将谓词应用于流来找到正确的答案。像这样:

    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.
    });

我不认为这是非常漂亮的代码,有没有更好的方法来实现这一点?

第 2 部分

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

提出的幼稚解决方案:

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);

这似乎更糟,无论是从美学角度还是因为增加了迭代。

第 3 部分

是否可以使用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,使得整个表达式的计算结果为“None found”,然后终止。

我想要的是从本质上获取与匹配中第一个谓词匹配的任何内容,并使用该值(如果存在),并且仅在找不到与第一个匹配项匹配的情况下才给我与第二个条件匹配的任何内容,并且依此类推,如果流中没有任何值与任何谓词匹配,则只给我默认值(“未找到”)。

必须有更好的方法来做到这一点,对吧?还是我只是在浪费时间尝试做一些最好留给更传统的命令式风格的事情?

感谢您阅读我的问题!

【问题讨论】:

  • 请只问 1 个问题。你有 3 个完全不同的问题。
  • 在第 2 部分中,您应该使用 anyMatch

标签: java functional-programming java-8 vavr


【解决方案1】:

您可以创建一个类来封装谓词及其名称:

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);

现在我们可以按降序流过它们,并为每个i打印第一个匹配谓词的名称:

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);

【讨论】:

  • 很好,但是如果你需要 i18n 的话,像这样把名字拼凑在一起是行不通的
  • @PatrickParker 很公平,但您可以轻松地采取更明确的方法:NamedPredicate and(NamedPredicate other, String newName) { return new NamedPredicate(newName, this.predicate.and(other.predicate)); }
  • 为什么您没有考虑 divisibleByFourAndFive 的案例?
  • @sara 我刚刚复制了 OP 的列表。
  • @Patrick Parker:我看不出为什么这个操作应该受到 i18n 考虑的影响。您可以将“name”替换为“resourceBundleKey”,逻辑不会改变。或者您让NamedPredicate 在构造函数中直接查找资源包,因此您可以将“name”替换为“localizedName”。或者你使用资源注入……或者……
猜你喜欢
  • 1970-01-01
  • 2023-04-05
  • 2015-02-18
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-02-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多