Delet - cywongg/2025 GitHub Wiki

Below is a purely functional version of calculateSentenceScore that:

  1. Uses no explicit loops (for, while, do-while, forEach, etc.).
  2. Uses no explicit conditionals (if-else, switch, or ternary).
  3. Avoids splitting the stream pipeline or creating temporary variables outside the pipeline.
  4. Relies on Optional and stream operations to provide the necessary “conditional” behaviors.
public static double calculateSentenceScore(Map<String, Double> wordScores, String text) {
    return Optional.ofNullable(wordScores)
        // First, filter out a null or empty wordScores Map
        .filter(w -> !w.isEmpty())

        // Next, "flatMap" into an Optional of text
        .flatMap(w -> Optional.ofNullable(text)
            // Filter out a null or empty text
            .filter(t -> !t.isEmpty())
            .map(String::toLowerCase)

            // Now split into tokens and create a Stream<Double> of scores
            .map(t -> Arrays.stream(t.split(" "))
                // Filter tokens that are not empty and start with a letter
                .filter(token -> !token.isEmpty() && Character.isLetter(token.charAt(0)))
                // Convert each token to its associated score using Map::getOrDefault
                .mapToDouble(token -> w.getOrDefault(token, 0d))
                // Finally, compute the average score (returns OptionalDouble)
                .average()
                // If the stream was empty (no valid tokens), use 0.0
                .orElse(0.0)
            )
        )
        // If wordScores or text was invalid, fall back to 0.0
        .orElse(0.0);
}

Why This Is “Purely Functional” Under the Constraints

  • No loops: All iteration happens inside Arrays.stream(...) and the Stream methods.
  • No if-else or switch: We use .filter(...) calls and .orElse(...) instead of if-else.
  • No splitting the stream: The pipeline goes from Optional.ofNullable(wordScores).flatMap(...).map(...).mapToDouble(...). We only have nested calls, but it’s all a single flow.
  • No temporary variables outside: We rely on the Stream pipeline and Optional transformations; no double total = 0; or similar.

Common Follow-Up Questions

  1. Q: How do .filter(...) and .orElse(...) replace conditionals in functional programming?
    A: Instead of if-else, we use .filter(...) to keep only items that match a predicate, and .orElse(...) to provide defaults when an Optional is empty.

  2. Q: Why can we not just write if (text == null) return 0; in functional style?
    A: The requirement prohibits explicit conditionals. Functional style uses Optional.ofNullable(text).filter(...) or .orElse() to handle null or missing values.

  3. Q: How does .average() help us avoid having to manually sum and count tokens?
    A: .average() in a DoubleStream automatically computes the average. If the stream is empty (i.e., no valid tokens), it returns an OptionalDouble.empty(). We then call .orElse(0.0) to avoid exceptions or manual checks.

  4. Q: Is .orElse(0.0) considered a conditional statement?
    A: No. .orElse(...) is a method call on Optional or OptionalDouble, which is allowed in functional style. It provides a fallback value if the Optional is not present.

  5. Q: Why use .flatMap(...) when nesting Optional calls?
    A: .flatMap(...) prevents having an Optional<Optional<T>>. It unpacks the outer Optional and gives you a nice chain of transformations without manual unwrapping.

By reviewing both the code example and the follow-up Q&A, you can deepen your understanding of how to translate from a loop/conditional-heavy imperative style to a more declarative, functional style using Java Streams and Optional.

⚠️ **GitHub.com Fallback** ⚠️