0

How do you do the equivalent of the following transform() method using pure functional programming (without the if-conditional).

Meta: I'd appreciate a title edit, I'm not sure how to word this question in "functionalese"

public class Playground {

    private static Optional<Map<String,Integer>> transform(List<Tuple<String,Optional<Integer>>> input) {
        if (input.stream().anyMatch(t->t.second.isEmpty())) return Optional.empty();

        Map<String, Integer> theMap = input.stream()
                .map(t -> new Tuple<>(t.first, t.second.get()))
                .collect(Collectors.groupingBy(
                        t1 -> t1.first,
                        Collectors.mapping(t2 -> t2.second, toSingle())));
        return Optional.of(theMap);
    }

    @Test
    public void collect()  {
        List<Tuple<String,Optional<Integer>>> input1 = new ArrayList<>();
        input1.add(new Tuple<>("foo", Optional.of(1)));
        input1.add(new Tuple<>("bar", Optional.empty()));

        Optional<Map<String,Integer>> result1 = transform(input1);

        assertTrue(result1.isEmpty());

        List<Tuple<String,Optional<Integer>>> input2 = new ArrayList<>();
        input2.add(new Tuple<>("foo", Optional.of(1)));
        input2.add(new Tuple<>("bar", Optional.of(2)));

        Optional<Map<String,Integer>> result2 = transform(input2);

        assertTrue(result2.isPresent());
        assertEquals((int)1, (int)result2.get().get("foo"));
        assertEquals((int)2, (int)result2.get().get("bar"));
    }

    private static class Tuple<T1,T2> {
        public T1 first;
        public T2 second;
        public Tuple(T1 first, T2 second) {
            this.first = first;
            this.second = second;
        }
    }

    public static <T> Collector<T, ?, T> toSingle() {
        return Collectors.collectingAndThen(
                Collectors.toList(),
                list ->  list.get(0)
        );
    }
}
4
  • The purpose of the sole if statement seems to be to select between two alternative return values. I'm unsure whether it meets your criteria, but it should be possible to use the ternary operator instead. Commented Sep 24, 2020 at 19:27
  • 1
    Alternatively, you should be able to coerce Optional.filter() into doing the job of the if, following up with Optional.map() to perform the computation in the case that you actually want to do that. Commented Sep 24, 2020 at 19:33
  • 1
    Instead of .map(t -> new Tuple<>(t.first, t.second.get())) .collect(Collectors.groupingBy(t1 -> t1.first, Collectors.mapping(t2 -> t2.second, toSingle()))) you can simply use .collect(Collectors.toMap(t -> t.first, t -> t.second.get(), (a,b) -> a)) Commented Sep 25, 2020 at 8:29
  • @Holger Thank you, a useful simplification! Commented Sep 25, 2020 at 16:12

3 Answers 3

1

This might work for you:

  private static Optional<Map<String, Integer>> transform(
      List<Tuple<String, Optional<Integer>>> input) {
    return Optional.of(input)
        .filter(t -> t.stream().allMatch(a -> a.second.isPresent()))
        .map(
            in ->
                in.stream()
                    .filter(t -> t.second.isPresent())
                    .map(t -> new Tuple<>(t.first, t.second.get()))
                    .collect(
                        Collectors.groupingBy(
                            t1 -> t1.first, Collectors.mapping(t2 -> t2.second, toSingle()))));
  }
Sign up to request clarification or add additional context in comments.

3 Comments

This is the answer, or very close. Is there a way to avoid double-iteration of the stream? (Imagine the method parameter is of type Stream<> rather than List<>.
Nope, I couldn't come up with a clean solution to short-circuit like that. I would go with a 2-step approach or via good old loops with early returns.
Thanks, I believe this is exactly the purpose of Optional::filter.
1

Although my solution does not satisfy your result, I can offer a solution with the ternary operator

private static Map<String, Integer> transform(List<Tuple<String, Optional<Integer>>> input) {
    return input.stream().anyMatch(t -> t.second.isEmpty()) ? Collections.emptyMap() :
            input.stream()
                    .map(t -> new Tuple<>(t.first, t.second.get()))
                    .collect(Collectors.groupingBy(
                            t1 -> t1.first,
                            Collectors.mapping(t2 -> t2.second, toSingle())));
}

2 Comments

If iterating twice is not a majorly impacting concern for the OP, I would still recommend following the method signature to get rid of Optional wrapped Map.
Totally agree with you but the question here is to find a solution with functional programming
1

“pure functional programming” is not necessarily a sign of quality and not an end in itself.

If you want to make the code simpler and more efficient, which may include getting rid of the if-conditional, especially as it bears a second iteration over the source data, you can do it in various ways. E.g.

private static <K,V> Optional<Map<K,V>> transform(List<Tuple<K,Optional<V>>> input) {
    final class AbsentValue extends RuntimeException {
        AbsentValue() { super(null, null, false, false); }
    }

    try {
        return Optional.of(input.stream().collect(Collectors.toMap(
            t1 -> t1.first,
            t2 -> t2.second.orElseThrow(AbsentValue::new),
            (first,next) -> first)));
    } catch(AbsentValue av) {
        return Optional.empty();
    }
}

When empty optionals are truly the exceptional case, you can make flagging via exception part of the method’s contract, e.g.

public static class AbsentValueException extends RuntimeException {

}
private static <K,V> Map<K,V> transform(List<Tuple<K,Optional<V>>> input)
    throws AbsentValueException {

    return input.stream().collect(Collectors.toMap(
        t1 -> t1.first,
        t2 -> t2.second.orElseThrow(AbsentValueException::new),
        (first,next)->first));
}
@Test(expected = AbsentValueException.class)
public void collect1() {
    List<Tuple<String,Optional<Integer>>> input1 = new ArrayList<>();
    input1.add(new Tuple<>("foo", Optional.of(1)));
    input1.add(new Tuple<>("bar", Optional.empty()));

    Map<String,Integer> result1 = transform(input1);
}

@Test
public void collect2() {
    List<Tuple<String,Optional<Integer>>> input2 = new ArrayList<>();
    input2.add(new Tuple<>("foo", Optional.of(1)));
    input2.add(new Tuple<>("bar", Optional.of(2)));

    Map<String,Integer> result2 = transform(input2);

    assertEquals((int)1, (int)result2.get("foo"));
    assertEquals((int)2, (int)result2.get("bar"));
}

Even better would be not to put optionals into the list of tuples in the first place.

3 Comments

"pure functional programming is not necessarily a sign of quality and not an end in itself" - Yes. Also, the Sun rises in the East and sets in the West. Water is wet.
Very early in "The Java Programming Language" book James Gosling, et, al, clearly say exceptions are not meant to be used for control flow. For similar reasons I'd also check your assumption it is more efficient.
@GarrettSmith Well, James Gosling surely never said you ought to use Optional for control flow either. As said in my answer, even better would be not to put optionals into the list of tuples in the first place. If you fix the flaw there, there is no need to deal with the situation later-on. The question is, whether having empty optionals in your tuples is an exceptional situation or not. The fact that you do not want to recover any value in that situation suggests that they are exceptional. And an expert did analyze the performance: shipilev.net/blog/2014/exceptional-performance

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.