Skip to content
Closed
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions NEWS.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +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]`
* 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])
Expand Down
37 changes: 37 additions & 0 deletions base/array.jl
Original file line number Diff line number Diff line change
Expand Up @@ -2450,6 +2450,43 @@ 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)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Q for anyone: is eachindex and nextind / iterate guaranteed to be in the same visit order?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@inbounds begin
if m[i]
i == j || (a[j] = a[i])
j = nextind(a, j)
end
end
end
j > lastindex(a) && return a
deleteat!(a, j:lastindex(a))
return a
end

# set-like operators for vectors
# These are moderately efficient, preserve order, and remove dupes.

Expand Down
1 change: 1 addition & 0 deletions base/exports.jl
Original file line number Diff line number Diff line change
Expand Up @@ -532,6 +532,7 @@ export
mapfoldl,
mapfoldr,
mapreduce,
mask!,
merge!,
mergewith!,
merge,
Expand Down
1 change: 1 addition & 0 deletions doc/src/base/collections.md
Original file line number Diff line number Diff line change
Expand Up @@ -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!
Expand Down
26 changes: 26 additions & 0 deletions test/arrayops.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand Down