From 565bc044553a2bec08821a3b824287109390deab Mon Sep 17 00:00:00 2001 From: "Viral B. Shah" Date: Tue, 13 Oct 2026 03:06:47 +0000 Subject: [PATCH 1/2] Fold `init` into `mapreduce` with `|` or `&` along the rows Co-Authored-By: Claude Opus 5.5 --- src/reductions.jl | 36 +++++++++++++++++------------------- test/reductions.jl | 5 +++++ 2 files changed, 22 insertions(+), 19 deletions(-) diff --git a/src/reductions.jl b/src/reductions.jl index 3930d243..b29b420d 100644 --- a/src/reductions.jl +++ b/src/reductions.jl @@ -590,37 +590,35 @@ function _mapreducecols!(f, op::typeof(+), R::AbstractArray, A::SparseMatrixCSCO end # any(pred, A, dims = 1) => mapreduce(pred, |, A, dims = 1) -# final argument `post` is to allow post-mapping each columnar mapreduce +# `R` holds the initial value of each column, which the column's reduction is folded into. +# With `negate`, `pred` is the negated predicate of an `&` reduction, see below. function _mapreducerows!(pred::P, ::typeof(|), R::AbstractMatrix{Bool}, A::SparseMatrixCSCOrColumnSubset{Tv}, - post::F = identity) where {P, F, Tv} + negate::Bool = false) where {P, Tv} nzval = getnzval(A) m, n = size(A) @inbounds for ii in axes(A,2) + # an initial value that already decides the column + R[1, ii] == negate || continue rng = getnzrange(A, ii) len = length(rng) - # An empty column is trivial - if len == 0 - R[1, ii] = post(pred(zero(Tv))) - continue - end - # If predicate on zero is true, then sparse column can be short-circuited - if pred(zero(Tv)) && len < m - R[1, ii] = post(true) - continue - end - # Otherwise reduce over the stored values - r = false - for jj in rng - r = pred(nzval[jj]) - r && break + if len < m && pred(zero(Tv)) + # an unstored entry satisfies the predicate + r = true + else + # reduce over the stored values + r = false + for jj in rng + r = pred(nzval[jj]) + r && break + end end - R[1, ii] = post(r) + R[1, ii] = r != negate end return R end # all(pred, A, dims = 1) => mapreduce(pred, &, A, dims = 1) == .!mapreduce(!pred, |, A, dims = 1) _mapreducerows!(pred::P, ::typeof(&), R::AbstractMatrix{Bool}, - A::SparseMatrixCSCOrColumnSubset) where {P} = _mapreducerows!(!pred, |, R, A, !) + A::SparseMatrixCSCOrColumnSubset) where {P} = _mapreducerows!(!pred, |, R, A, true) # findmax/min and argmax/min methods # find the first entry of the sparse matrix that is not stored - return its cartesian index diff --git a/test/reductions.jl b/test/reductions.jl index 24f310da..3eee2d4f 100644 --- a/test/reductions.jl +++ b/test/reductions.jl @@ -372,6 +372,11 @@ end @test all(pred, Bs, dims = 1) == all(pred, Bd, dims = 1) @test all(pred, Cs, dims = 1) == all(pred, Cd, dims = 1) end + # `|` and `&` along the rows fold into `init` + for (pred, op) in ((>(5), |), (!iszero, &)), init in (true, false), + D in ([1.0 2.0; 0.0 3.0], [1.0 2.0; 3.0 4.0], [0.0 6.0; 0.0 0.0]) + @test mapreduce(pred, op, sparse(D); dims=1, init) == mapreduce(pred, op, D; dims=1, init) + end end end From 23e1ae6b3014664f973e787c2a34a90e08d85c21 Mon Sep 17 00:00:00 2001 From: "Viral B. Shah" Date: Tue, 13 Oct 2026 03:18:02 +0000 Subject: [PATCH 2/2] Trim the `init` tests to one case per operator Co-Authored-By: Claude Opus 5.5 --- test/reductions.jl | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/test/reductions.jl b/test/reductions.jl index 3eee2d4f..24a1ec37 100644 --- a/test/reductions.jl +++ b/test/reductions.jl @@ -373,10 +373,9 @@ end @test all(pred, Cs, dims = 1) == all(pred, Cd, dims = 1) end # `|` and `&` along the rows fold into `init` - for (pred, op) in ((>(5), |), (!iszero, &)), init in (true, false), - D in ([1.0 2.0; 0.0 3.0], [1.0 2.0; 3.0 4.0], [0.0 6.0; 0.0 0.0]) - @test mapreduce(pred, op, sparse(D); dims=1, init) == mapreduce(pred, op, D; dims=1, init) - end + D = [1.0 2.0; 0.0 3.0] + @test mapreduce(>(5), |, sparse(D); dims=1, init=true) == mapreduce(>(5), |, D; dims=1, init=true) + @test mapreduce(!iszero, &, sparse(D); dims=1, init=false) == mapreduce(!iszero, &, D; dims=1, init=false) end end