Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Reduce Step

The reduce step combines all elements in a traversal using a binary operation, returning a single result element. It repeatedly applies a function to pairs of elements, keeping one from each pair, until only one element remains.

Reduce step diagram showing pairwise comparison reducing elements to a single result

In this diagram:

  • An Input Stream contains elements A (age=30), B (age=25), and C (age=40).
  • The .reduce(|acc, v| ...) step is applied, using a reducer function that keeps the element with the maximum age.
  • The Reduction Process visualization shows the pairwise comparisons:
    • A vs B: Element A is kept (30 > 25), B is discarded.
    • (A) vs C: Element C is kept (40 > 30), A is discarded.
  • The Result of the reduction process is the single remaining element (C), which continues in the walker chain.

Syntax

walker.reduce(|accumulated, next_element, context| {
    // combine elements and return either accumulated or next_element
})

Parameters

  • reducer: A function that takes:
    • The accumulated element from previous reduction steps
    • The next element to be considered
    • The parent walker's context (immutable)
    • Returns a ControlFlow with either the accumulated or next element

Return Value

Returns an Option containing the result element if the traversal is not empty, or None if the traversal is empty.

Example

use crate::standard_model::{Person, VertexExt, standard_populated_graph};
use graph_api_lib::{Graph, VertexReference, VertexSearch};

pub fn reduce_example() {
    // Create a graph with standard test data
    let graph = standard_populated_graph();

    // Find the oldest person in the graph using reduce
    let oldest = graph
        .walk()
        .vertices(VertexSearch::scan())
        .filter_person()
        .reduce(|acc, vertex, _ctx| {
            let acc_age = if let Some(person) = acc.project::<Person<_>>() {
                person.age()
            } else {
                0
            };
            let vertex_age = if let Some(person) = vertex.project::<Person<_>>() {
                person.age()
            } else {
                0
            };

            // Return the person with higher age
            if vertex_age > acc_age { vertex } else { acc }
        })
        .map(|vertex, _ctx| {
            if let Some(person) = vertex.project::<Person<_>>() {
                format!(
                    "The oldest person is {:?}, age {}",
                    vertex.id(),
                    person.age()
                )
            } else {
                format!("Unexpected non-person vertex: {:?}", vertex.id())
            }
        })
        .next()
        .expect("Should find at least one person");

    println!("{}", oldest);
}

Best Practices

  • Design reducers that follow associative properties when possible
  • Handle empty traversals by checking for None in the result
  • The reducer can only select one of the elements, not create new ones
  • Use ControlFlow::Continue to keep reducing, and ControlFlow::Break to halt early
  • Consider fold instead when you need to build a new value rather than select among elements (note: fold terminates the walker)
  • Remember that unlike the old API, the context is immutable in the reducer function

Common Use Cases

  • Extrema finding: Selecting maximum or minimum elements by some property
  • Best match selection: Choosing the most relevant element from a set of results
  • Representative selection: Picking a single representative element from similar options
  • Priority determination: Finding highest/lowest priority elements in a graph