3

and thanks for checking out my question!

I'm having a little trouble wrapping my head around using Streams with multiple predicates being applied in specific order.

For the sake of example, please consider the following 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.

Part 1

I have converted the problem I have down to a "FizzBuzz"-esque version, trying to find the correct answer by applying the predicates to a stream in a specific order. Like so:

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

I don't think this is very pretty code, is there a better way of achieving this?

Part 2

How about if I really need to apply the predicates to see if an appropriate value is present in the stream, and computing a related value to be returned(in this case, a string to print). How would that even look?

A proposed naive solution:

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

This seems even worse, both aesthetically and because of the added iterations.

Part 3

Could this possibly be solved in a prettier, more efficient way using 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);

The obvious problem here is the ".findFirst()", which would be the Integer 1 on this case, making the entire expression evaluating to "None found", and then terminating.

What I want is to essentially grab anything that matches the very first predicate in my match, and use that value, if it exists, and only give me whatever matches the second Case if no matches for the first one are found, and so on, only giving me the default("None found"), if no value in the stream matches any predicate.

There has to be a better way of doing this, right? Or am I just wasting my time trying to do something that is just better left to a more traditional, imperative style?

Thank you for reading my question!

Raudbjorn
  • 450
  • 2
  • 8
  • 1
    Please only ask 1 question. You've got 3 entirely separate questions. – 4castle Feb 27 '17 at 21:21
  • 1
    In part 2 you should be using [`anyMatch`](https://docs.oracle.com/javase/8/docs/api/java/util/stream/IntStream.html#anyMatch-java.util.function.IntPredicate-). – 4castle Feb 27 '17 at 21:26

1 Answers1

8

You can create a class to encapsulate the predicate and its name:

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

The and() method allows us to compose them similar to the way you did originally:

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

Now we can stream through them in descending order and print the name of the first matching predicate, for each 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);
shmosel
  • 49,289
  • 6
  • 73
  • 138
  • nice, but hacking the names together like that is not going to work if you ever need i18n – Patrick Parker Feb 27 '17 at 22:04
  • 1
    @PatrickParker Fair enough, but you could easily take a more explicit approach: `NamedPredicate and(NamedPredicate other, String newName) { return new NamedPredicate(newName, this.predicate.and(other.predicate)); }` – shmosel Feb 27 '17 at 22:06
  • Why you did not consider a case for divisibleByFourAndFive? – sara Feb 27 '17 at 22:13
  • 1
    @sara I just copied OP's list. – shmosel Feb 27 '17 at 22:14
  • @shmosel thank yout! this is great! I had probably tried to enumerate them into a Tuple and ordering them if you hadn't pointed out this solution! – Raudbjorn Feb 27 '17 at 22:22
  • @sara The predicates themselves weren't really important, the only thing that mattered was that there would be an arbitrary number of them and more than one could apply to some of the values in the stream. That being said, I feel silly for having not added the one you mentioned -I must just be tired or something. – Raudbjorn Feb 27 '17 at 22:26
  • 1
    @Patrick Parker: I see no reason why this operation should be affected by i18n considerations at all. You can replace “`name`” with “`resourceBundleKey`” and the logic doesn’t change. Or you let `NamedPredicate` do the resource bundle lookup right in the constructor, so you can replace “`name`” with “`localizedName`”. Or you use resource injection… or… – Holger Feb 28 '17 at 09:34
  • @Holger I guess you could look up `this.name + " and " + other.name` in a resource bundle. But that would make it difficult to extract a complete list of translatable strings for your translators. Perhaps i18n-friendliness is too off topic for this answer; still I felt it worth mentioning, since it relates to best practices. – Patrick Parker Feb 28 '17 at 10:15