Skip to content

Commit 2da9ddb

Browse files
authored
Change find() to return the same index type as pairs() (#24774)
This does not change anything for AbstractVector, Tuple and general iterables, which continue to use linear indices. For other AbstractArrays, return CartesianIndexes (rather than linear indices). For AbstractDict, AbstractString and NamedTuple, return keys (previously not supported at all). Relying on collect() to choose the return element type allows supporting any definition of pairs(), including that for Dict, which creates a standard Generator for which eltype() returns Any.
1 parent a06cb6c commit 2da9ddb

File tree

8 files changed

+84
-49
lines changed

8 files changed

+84
-49
lines changed

NEWS.md

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -362,13 +362,19 @@ This section lists changes that do not have deprecation warnings.
362362
trait; see its documentation for details. Types which support subtraction (operator
363363
`-`) must now implement `widen` for hashing to work inside heterogeneous arrays.
364364

365-
* `AbstractSet` objects are now considered equal by `==` and `isequal` if all of their
365+
* `findn(x::AbstractVector)` now returns a 1-tuple with the vector of indices, to be
366+
consistent with higher order arrays ([#25365]).
367+
368+
* `find` now returns the same type of indices as `keys`/`pairs` for `AbstractArray`,
369+
`AbstractDict`, `AbstractString`, `Tuple` and `NamedTuple` objects ([#24774]).
370+
In particular, this means that it returns `CartesianIndex` objects for matrices
371+
and higher-dimensional arrays instead of linear indices as was previously the case.
372+
Use `Int[LinearIndices(size(a))[i] for i in find(f, a)]` to compute linear indices.
373+
374+
* `AbstractSet` objects are now considered equal by `==` and `isequal` if all of their
366375
elements are equal ([#25368]). This has required changing the hashing algorithm
367376
for `BitSet`.
368377

369-
* `findn(x::AbstractVector)` now return a 1-tuple with the vector of indices, to be
370-
consistent with higher order arrays ([#25365]).
371-
372378
* the default behavior of `titlecase` is changed in two ways ([#23393]):
373379
+ characters not starting a word are converted to lowercase;
374380
a new keyword argument `strict` is added which
@@ -377,7 +383,6 @@ This section lists changes that do not have deprecation warnings.
377383
to get the old behavior (only "space" characters are considered as
378384
word separators), use the keyword `wordsep=isspace`.
379385

380-
381386
Library improvements
382387
--------------------
383388

@@ -1155,6 +1160,7 @@ Command-line option changes
11551160
[#24713]: https://github.com/JuliaLang/julia/issues/24713
11561161
[#24714]: https://github.com/JuliaLang/julia/issues/24714
11571162
[#24715]: https://github.com/JuliaLang/julia/issues/24715
1163+
[#24774]: https://github.com/JuliaLang/julia/issues/24774
11581164
[#24781]: https://github.com/JuliaLang/julia/issues/24781
11591165
[#24785]: https://github.com/JuliaLang/julia/issues/24785
11601166
[#24786]: https://github.com/JuliaLang/julia/issues/24786

base/array.jl

Lines changed: 43 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -1720,48 +1720,60 @@ findlast(testf::Function, A) = findprev(testf, A, endof(A))
17201720
"""
17211721
find(f::Function, A)
17221722
1723-
Return a vector `I` of the linear indices of `A` where `f(A[I])` returns `true`.
1723+
Return a vector `I` of the indices or keys of `A` where `f(A[I])` returns `true`.
17241724
If there are no such elements of `A`, return an empty array.
17251725
1726+
Indices or keys are of the same type as those returned by [`keys(A)`](@ref)
1727+
and [`pairs(A)`](@ref) for `AbstractArray`, `AbstractDict`, `AbstractString`
1728+
`Tuple` and `NamedTuple` objects, and are linear indices starting at `1`
1729+
for other iterables.
1730+
17261731
# Examples
17271732
```jldoctest
1733+
julia> x = [1, 3, 4]
1734+
3-element Array{Int64,1}:
1735+
1
1736+
3
1737+
4
1738+
1739+
julia> find(isodd, x)
1740+
2-element Array{Int64,1}:
1741+
1
1742+
2
1743+
17281744
julia> A = [1 2 0; 3 4 0]
17291745
2×3 Array{Int64,2}:
17301746
1 2 0
17311747
3 4 0
1732-
17331748
julia> find(isodd, A)
1734-
2-element Array{Int64,1}:
1735-
1
1736-
2
1749+
2-element Array{CartesianIndex{2},1}:
1750+
CartesianIndex(1, 1)
1751+
CartesianIndex(2, 1)
17371752
17381753
julia> find(!iszero, A)
1739-
4-element Array{Int64,1}:
1740-
1
1741-
2
1742-
3
1743-
4
1754+
4-element Array{CartesianIndex{2},1}:
1755+
CartesianIndex(1, 1)
1756+
CartesianIndex(2, 1)
1757+
CartesianIndex(1, 2)
1758+
CartesianIndex(2, 2)
1759+
1760+
julia> d = Dict(:A => 10, :B => -1, :C => 0)
1761+
Dict{Symbol,Int64} with 3 entries:
1762+
:A => 10
1763+
:B => -1
1764+
:C => 0
1765+
1766+
julia> find(x -> x >= 0, d)
1767+
2-element Array{Symbol,1}:
1768+
:A
1769+
:C
17441770
1745-
julia> find(isodd, [2, 4])
1746-
0-element Array{Int64,1}
17471771
```
17481772
"""
1749-
function find(testf::Function, A)
1750-
# use a dynamic-length array to store the indices, then copy to a non-padded
1751-
# array for the return
1752-
tmpI = Vector{Int}()
1753-
inds = _index_remapper(A)
1754-
for (i,a) = enumerate(A)
1755-
if testf(a)
1756-
push!(tmpI, inds[i])
1757-
end
1758-
end
1759-
I = Vector{Int}(uninitialized, length(tmpI))
1760-
copyto!(I, tmpI)
1761-
return I
1762-
end
1763-
_index_remapper(A::AbstractArray) = linearindices(A)
1764-
_index_remapper(iter) = OneTo(typemax(Int)) # safe for objects that don't implement length
1773+
find(testf::Function, A) = collect(first(p) for p in _pairs(A) if testf(last(p)))
1774+
1775+
_pairs(A::Union{AbstractArray, AbstractDict, AbstractString, Tuple, NamedTuple}) = pairs(A)
1776+
_pairs(iter) = zip(OneTo(typemax(Int)), iter) # safe for objects that don't implement length
17651777

17661778
"""
17671779
find(A)
@@ -1786,22 +1798,10 @@ julia> find(falses(3))
17861798
```
17871799
"""
17881800
function find(A)
1789-
nnzA = count(t -> t != 0, A)
1790-
I = Vector{Int}(uninitialized, nnzA)
1791-
cnt = 1
1792-
inds = _index_remapper(A)
1793-
warned = false
1794-
for (i,a) in enumerate(A)
1795-
if !warned && !(a isa Bool)
1796-
depwarn("In the future `find(A)` will only work on boolean collections. Use `find(x->x!=0, A)` instead.", :find)
1797-
warned = true
1798-
end
1799-
if a != 0
1800-
I[cnt] = inds[i]
1801-
cnt += 1
1802-
end
1801+
if !(eltype(A) === Bool) && !all(x -> x isa Bool, A)
1802+
depwarn("In the future `find(A)` will only work on boolean collections. Use `find(x->x!=0, A)` instead.", :find)
18031803
end
1804-
return I
1804+
collect(first(p) for p in _pairs(A) if last(p) != 0)
18051805
end
18061806

18071807
find(x::Bool) = x ? [1] : Vector{Int}()

base/sparse/sparsematrix.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1276,7 +1276,7 @@ function find(p::Function, S::SparseMatrixCSC)
12761276
end
12771277
sz = size(S)
12781278
I, J = _findn(p, S)
1279-
return Base._sub2ind(sz, I, J)
1279+
return CartesianIndex.(I, J)
12801280
end
12811281
find(p::Base.OccursIn, x::SparseMatrixCSC) =
12821282
invoke(find, Tuple{Base.OccursIn, AbstractArray}, p, x)

test/arrayops.jl

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -457,6 +457,12 @@ end
457457
@test findnext(equalto(0x00), [0x00, 0x01, 0x00], 2) == 3
458458
@test findprev(equalto(0x00), [0x00, 0x01, 0x00], 2) == 1
459459
end
460+
@testset "find with Matrix" begin
461+
A = [1 2 0; 3 4 0]
462+
@test find(isodd, A) == [CartesianIndex(1, 1), CartesianIndex(2, 1)]
463+
@test find(!iszero, A) == [CartesianIndex(1, 1), CartesianIndex(2, 1),
464+
CartesianIndex(1, 2), CartesianIndex(2, 2)]
465+
end
460466
@testset "find with general iterables" begin
461467
s = "julia"
462468
@test find(c -> c == 'l', s) == [3]

test/dict.jl

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -757,3 +757,10 @@ end
757757
end
758758
@test map(string, keys(d)) == Set(["1","3"])
759759
end
760+
761+
@testset "find" begin
762+
@test @inferred find(equalto(1), Dict(:a=>1, :b=>2)) == [:a]
763+
@test @inferred sort(find(equalto(1), Dict(:a=>1, :b=>1))) == [:a, :b]
764+
@test @inferred isempty(find(equalto(1), Dict()))
765+
@test @inferred isempty(find(equalto(1), Dict(:a=>2, :b=>3)))
766+
end

test/namedtuple.jl

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -208,3 +208,8 @@ abstr_nt_22194_3()
208208
@test Base.structdiff((a=1, b=2, z=20), NamedTuple{(:b,)}) == (a=1, z=20)
209209
@test typeof(Base.structdiff(NamedTuple{(:a, :b), Tuple{Int32, Union{Int32, Nothing}}}((1, Int32(2))),
210210
(a=0,))) === NamedTuple{(:b,), Tuple{Union{Int32, Nothing}}}
211+
212+
@test @inferred find(equalto(1), (a=1, b=2)) == [:a]
213+
@test @inferred find(equalto(1), (a=1, b=1)) == [:a, :b]
214+
@test @inferred isempty(find(equalto(1), NamedTuple()))
215+
@test @inferred isempty(find(equalto(1), (a=2, b=3)))

test/strings/search.jl

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -324,3 +324,7 @@ end
324324
@test findnext(equalto('('), "(⨳(", 2) == 5
325325
@test findlast(equalto('('), "(⨳(") == 5
326326
@test findprev(equalto('('), "(⨳(", 2) == 1
327+
328+
@test @inferred find(equalto('a'), "éa") == [3]
329+
@test @inferred find(equalto(''), "€€") == [1, 4]
330+
@test @inferred isempty(find(equalto('é'), ""))

test/tuple.jl

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -364,3 +364,10 @@ end
364364
@testset "issue 24707" begin
365365
@test eltype(Tuple{Vararg{T}} where T<:Integer) >: Integer
366366
end
367+
368+
@testset "find" begin
369+
@test @inferred find(equalto(1), (1, 2)) == [1]
370+
@test @inferred find(equalto(1), (1, 1)) == [1, 2]
371+
@test @inferred isempty(find(equalto(1), ()))
372+
@test @inferred isempty(find(equalto(1), (2, 3)))
373+
end

0 commit comments

Comments
 (0)