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.
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, andControlFlow::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