From a0d354507b32ad53b971a57039c77885e86486c4 Mon Sep 17 00:00:00 2001 From: MasonProtter Date: Thu, 4 Feb 2021 19:32:26 -0700 Subject: [PATCH 1/6] add mask! for in-place logical filtering --- base/array.jl | 20 ++++++++++++++++++++ base/exports.jl | 1 + test/arrayops.jl | 26 ++++++++++++++++++++++++++ 3 files changed, 47 insertions(+) diff --git a/base/array.jl b/base/array.jl index e81d26294c5d8..66396cb2de2f7 100644 --- a/base/array.jl +++ b/base/array.jl @@ -2436,6 +2436,26 @@ function filter!(f, a::AbstractVector) return a end +function mask!(a::AbstractVector, m::AbstractVector{Bool}) + j = firstindex(a) + for i in eachindex(a, m) + @inbounds begin + ai = a[i] + mi = m[i] + a[j] = ai + end + j = ifelse(mi, nextind(a, j), j) + end + j > lastindex(a) && return a + if a isa Vector + resize!(a, j-1) + sizehint!(a, j-1) + else + deleteat!(a, j:lastindex(a)) + end + return a +end + # set-like operators for vectors # These are moderately efficient, preserve order, and remove dupes. diff --git a/base/exports.jl b/base/exports.jl index 440b28fb155b2..b87b2da5c3763 100644 --- a/base/exports.jl +++ b/base/exports.jl @@ -528,6 +528,7 @@ export mapfoldl, mapfoldr, mapreduce, + mask!, merge!, mergewith!, merge, diff --git a/test/arrayops.jl b/test/arrayops.jl index 5228c390d9339..8b9a1c0d19e49 100644 --- a/test/arrayops.jl +++ b/test/arrayops.jl @@ -1442,6 +1442,32 @@ end @test isempty(eoa) end +@testset "mask!" begin + # base case w/ Vector + a = Vector(1:10) + mask!(a, [falses(5); trues(5)]) + @test a == 6:10 + + # different subtype of AbstractVector + ba = rand(10) .> 0.5 # + @test isa(ba, BitArray) + mask!(ba, ba) + @test all(ba) + + # empty array + ea = [] + mask!(ea, Bool[]) + @test isempty(ea) + + # non-1-indexed array + # deleteat! is not supported for OffsetArrays + + # empty non-1-indexed array + eoa = OffsetArray([], -5) + mask!(eoa, Bool[]) + @test isempty(eoa) +end + @testset "deleteat!" begin for idx in Any[1, 2, 5, 9, 10, 1:0, 2:1, 1:1, 2:2, 1:2, 2:4, 9:8, 10:9, 9:9, 10:10, 8:9, 9:10, 6:9, 7:10] From bf54e57cf7bd83ec86dabfce930d0b69de660640 Mon Sep 17 00:00:00 2001 From: MasonProtter Date: Sat, 6 Feb 2021 12:44:19 -0700 Subject: [PATCH 2/6] add documentation --- NEWS.md | 1 + base/array.jl | 22 ++++++++++++++++++++++ 2 files changed, 23 insertions(+) diff --git a/NEWS.md b/NEWS.md index 60e9a07271559..09cf85678614c 100644 --- a/NEWS.md +++ b/NEWS.md @@ -34,6 +34,7 @@ New library functions * `isunordered(x)` returns true if `x` is value that is normally unordered, such as `NaN` or `missing`. * New macro `Base.@invokelatest f(args...; kwargs...)` provides a convenient way to call `Base.invokelatest(f, args...; kwargs...)` ([#37971]) * New macro `Base.@invoke f(arg1::T1, arg2::T2; kwargs...)` provides an easier syntax to call `invoke(f, Tuple{T1,T2}; kwargs...)` ([#38438]) +* New function `mask!(a::AbstractVector, m::AbstractVector{Bool})` which provides an in-place equivalent to the logical indexing expression `a = a[m]` New library features -------------------- diff --git a/base/array.jl b/base/array.jl index 66396cb2de2f7..9724e468f242c 100644 --- a/base/array.jl +++ b/base/array.jl @@ -2436,6 +2436,28 @@ function filter!(f, a::AbstractVector) return a end +""" + mask!(a::AbstractVector, m::AbstractVector{Bool}) + +The inplace version of logical indexing `a = a[m]`. That is, `mask!(a, m)` on +vectors of equal length `a` and `m` will remove all elements from `a` for which +`m` at the corresponding index is `false`. + +# Examples +```jldoctest +julia> a = [:a, :b, :c]; + +julia> mask!(a, [true, false, true]) +2-element Vector{Symbol}: + :a + :c + +julia> a +2-element Vector{Symbol}: + :a + :c +``` +""" function mask!(a::AbstractVector, m::AbstractVector{Bool}) j = firstindex(a) for i in eachindex(a, m) From d5c7c9671ad16bef63038aacdd53c73bf3a3add6 Mon Sep 17 00:00:00 2001 From: Mason Protter Date: Fri, 16 Apr 2021 14:33:57 -0600 Subject: [PATCH 3/6] Update base/array.jl Co-authored-by: Jameson Nash --- base/array.jl | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/base/array.jl b/base/array.jl index 9724e468f242c..9e62d40b1fd6c 100644 --- a/base/array.jl +++ b/base/array.jl @@ -2462,11 +2462,11 @@ function mask!(a::AbstractVector, m::AbstractVector{Bool}) j = firstindex(a) for i in eachindex(a, m) @inbounds begin - ai = a[i] - mi = m[i] - a[j] = ai + if m[i] + i == j || (a[j] = a[i]) + j = nextind(a, j) + end end - j = ifelse(mi, nextind(a, j), j) end j > lastindex(a) && return a if a isa Vector From 46c85334153baee722bff238b70363a3fe836215 Mon Sep 17 00:00:00 2001 From: Mason Protter Date: Fri, 16 Apr 2021 14:34:47 -0600 Subject: [PATCH 4/6] Update base/array.jl Co-authored-by: Jameson Nash --- base/array.jl | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/base/array.jl b/base/array.jl index 9e62d40b1fd6c..e509de1cb6a02 100644 --- a/base/array.jl +++ b/base/array.jl @@ -2469,12 +2469,7 @@ function mask!(a::AbstractVector, m::AbstractVector{Bool}) end end j > lastindex(a) && return a - if a isa Vector - resize!(a, j-1) - sizehint!(a, j-1) - else - deleteat!(a, j:lastindex(a)) - end + deleteat!(a, j:lastindex(a)) return a end From 9fe4130b1cbd1ff543732870ef790a25fa21dca3 Mon Sep 17 00:00:00 2001 From: Mason Protter Date: Fri, 16 Apr 2021 15:25:21 -0600 Subject: [PATCH 5/6] reference `Base.mask!` in docs --- doc/src/base/collections.md | 1 + 1 file changed, 1 insertion(+) diff --git a/doc/src/base/collections.md b/doc/src/base/collections.md index fe4d8f80b1cd0..822b8e7ffca2b 100644 --- a/doc/src/base/collections.md +++ b/doc/src/base/collections.md @@ -135,6 +135,7 @@ Base.collect(::Any) Base.collect(::Type, ::Any) Base.filter Base.filter! +Base.mask! Base.replace(::Any, ::Pair...) Base.replace(::Base.Callable, ::Any) Base.replace! From 1c61e2a0321347c236089d314061dab88356b8db Mon Sep 17 00:00:00 2001 From: MasonProtter Date: Thu, 27 May 2021 20:30:46 -0600 Subject: [PATCH 6/6] rename to keepat! --- NEWS.md | 2 +- base/array.jl | 8 ++++---- base/exports.jl | 2 +- doc/src/base/collections.md | 2 +- test/arrayops.jl | 10 +++++----- 5 files changed, 12 insertions(+), 12 deletions(-) diff --git a/NEWS.md b/NEWS.md index 4bfad9444c218..8767bff2b6fba 100644 --- a/NEWS.md +++ b/NEWS.md @@ -41,7 +41,7 @@ New library functions * `isunordered(x)` returns true if `x` is value that is normally unordered, such as `NaN` or `missing`. * New macro `Base.@invokelatest f(args...; kwargs...)` provides a convenient way to call `Base.invokelatest(f, args...; kwargs...)` ([#37971]) * New macro `Base.@invoke f(arg1::T1, arg2::T2; kwargs...)` provides an easier syntax to call `invoke(f, Tuple{T1,T2}; kwargs...)` ([#38438]) -* New function `mask!(a::AbstractVector, m::AbstractVector{Bool})` which provides an in-place equivalent to the logical indexing expression `a = a[m]` +* New function `keepat!(a::AbstractVector, m::AbstractVector{Bool})` which provides an in-place equivalent to the logical indexing expression `a = a[m]` * Two arguments method `lock(f, lck)` now accepts a `Channel` as the second argument. ([#39312]) * New functor `Returns(value)`, which returns `value` for any arguments ([#39794]) * New macro `Base.@invoke f(arg1::T1, arg2::T2; kwargs...)` provides an easier syntax to call `invoke(f, Tuple{T1,T2}, arg1, arg2; kwargs...)` ([#38438]) diff --git a/base/array.jl b/base/array.jl index 0b7c124b32d1d..3db77b8c7ca92 100644 --- a/base/array.jl +++ b/base/array.jl @@ -2451,9 +2451,9 @@ function filter!(f, a::AbstractVector) end """ - mask!(a::AbstractVector, m::AbstractVector{Bool}) + keepat!(a::AbstractVector, m::AbstractVector{Bool}) -The inplace version of logical indexing `a = a[m]`. That is, `mask!(a, m)` on +The inplace version of logical indexing `a = a[m]`. That is, `keepat!(a, m)` on vectors of equal length `a` and `m` will remove all elements from `a` for which `m` at the corresponding index is `false`. @@ -2461,7 +2461,7 @@ vectors of equal length `a` and `m` will remove all elements from `a` for which ```jldoctest julia> a = [:a, :b, :c]; -julia> mask!(a, [true, false, true]) +julia> keepat!(a, [true, false, true]) 2-element Vector{Symbol}: :a :c @@ -2472,7 +2472,7 @@ julia> a :c ``` """ -function mask!(a::AbstractVector, m::AbstractVector{Bool}) +function keepat!(a::AbstractVector, m::AbstractVector{Bool}) j = firstindex(a) for i in eachindex(a, m) @inbounds begin diff --git a/base/exports.jl b/base/exports.jl index cbcb7e8363f5c..126ffe1bfab3e 100644 --- a/base/exports.jl +++ b/base/exports.jl @@ -532,7 +532,7 @@ export mapfoldl, mapfoldr, mapreduce, - mask!, + keepat!, merge!, mergewith!, merge, diff --git a/doc/src/base/collections.md b/doc/src/base/collections.md index 822b8e7ffca2b..334e54f800e3f 100644 --- a/doc/src/base/collections.md +++ b/doc/src/base/collections.md @@ -135,7 +135,7 @@ Base.collect(::Any) Base.collect(::Type, ::Any) Base.filter Base.filter! -Base.mask! +Base.keepat! Base.replace(::Any, ::Pair...) Base.replace(::Base.Callable, ::Any) Base.replace! diff --git a/test/arrayops.jl b/test/arrayops.jl index 27a92b35974db..662fbefa26945 100644 --- a/test/arrayops.jl +++ b/test/arrayops.jl @@ -1442,21 +1442,21 @@ end @test isempty(eoa) end -@testset "mask!" begin +@testset "keepat!" begin # base case w/ Vector a = Vector(1:10) - mask!(a, [falses(5); trues(5)]) + keepat!(a, [falses(5); trues(5)]) @test a == 6:10 # different subtype of AbstractVector ba = rand(10) .> 0.5 # @test isa(ba, BitArray) - mask!(ba, ba) + keepat!(ba, ba) @test all(ba) # empty array ea = [] - mask!(ea, Bool[]) + keepat!(ea, Bool[]) @test isempty(ea) # non-1-indexed array @@ -1464,7 +1464,7 @@ end # empty non-1-indexed array eoa = OffsetArray([], -5) - mask!(eoa, Bool[]) + keepat!(eoa, Bool[]) @test isempty(eoa) end