Skip to content

Commit cabbb50

Browse files
committed
Improve inference for unique with abstract eltypes
#20317 improved inference of unique, but problematic cases still arise for containers with known but abstract eltypes. Here, we short-circuit the `typejoin` when the return type is determined by the element type of the input container. For `unique(f, itr)`, this commit also allows the caller to supply `seen::Set` to circumvent the inference challenges.
1 parent 6b2ffd3 commit cabbb50

File tree

3 files changed

+46
-22
lines changed

3 files changed

+46
-22
lines changed

NEWS.md

+2
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,8 @@ Standard library changes
4848
* `view`, `@view`, and `@views` now work on `AbstractString`s, returning a `SubString` when appropriate ([#35879]).
4949
* All `AbstractUnitRange{<:Integer}`s now work with `SubString`, `view`, `@view` and `@views` on strings ([#35879]).
5050
* `sum`, `prod`, `maximum`, and `minimum` now support `init` keyword argument ([#36188], [#35839]).
51+
* `unique(f, itr; seen=Set{T}())` now allows you to declare the container type used for
52+
keeping track of values returned by `f` on elements of `itr` ([#36280]).
5153

5254
#### LinearAlgebra
5355
* New method `LinearAlgebra.issuccess(::CholeskyPivoted)` for checking whether pivoted Cholesky factorization was successful ([#36002]).

base/set.jl

+31-13
Original file line numberDiff line numberDiff line change
@@ -120,19 +120,25 @@ julia> unique(Real[1, 1.0, 2])
120120
```
121121
"""
122122
function unique(itr)
123+
if isa(IteratorEltype(itr), HasEltype)
124+
T = eltype(itr)
125+
out = Vector{T}()
126+
seen = Set{T}()
127+
for x in itr
128+
if !in(x, seen)
129+
push!(seen, x)
130+
push!(out, x)
131+
end
132+
end
133+
return out
134+
end
123135
T = @default_eltype(itr)
124-
out = Vector{T}()
125-
seen = Set{T}()
126136
y = iterate(itr)
127-
y === nothing && return out
137+
y === nothing && return T[]
128138
x, i = y
129-
if !isconcretetype(T) && IteratorEltype(itr) == EltypeUnknown()
130-
S = typeof(x)
131-
return _unique_from(itr, S[x], Set{S}((x,)), i)
132-
end
133-
push!(seen, x)
134-
push!(out, x)
135-
return unique_from(itr, out, seen, i)
139+
S = typeof(x)
140+
R = isconcretetype(T) ? T : S
141+
return _unique_from(itr, R[x], Set{R}((x,)), i)
136142
end
137143

138144
_unique_from(itr, out, seen, i) = unique_from(itr, out, seen, i)
@@ -175,8 +181,18 @@ julia> unique(x -> x^2, [1, -1, 3, -3, 4])
175181
4
176182
```
177183
"""
178-
function unique(f, C)
184+
function unique(f, C; seen::Union{Nothing,Set}=nothing)
179185
out = Vector{eltype(C)}()
186+
if seen !== nothing
187+
for x in C
188+
y = f(x)
189+
if y seen
190+
push!(out, x)
191+
push!(seen, y)
192+
end
193+
end
194+
return out
195+
end
180196

181197
s = iterate(C)
182198
if s === nothing
@@ -241,15 +257,17 @@ julia> unique!(iseven, [2, 3, 5, 7, 9])
241257
3
242258
```
243259
"""
244-
function unique!(f, A::AbstractVector)
260+
function unique!(f, A::AbstractVector; seen::Union{Nothing,Set}=nothing)
245261
if length(A) <= 1
246262
return A
247263
end
248264

249265
i = firstindex(A)
250266
x = @inbounds A[i]
251267
y = f(x)
252-
seen = Set{typeof(y)}()
268+
if seen === nothing
269+
seen = Set{typeof(y)}()
270+
end
253271
push!(seen, y)
254272
return _unique!(f, A, seen, i, i+1)
255273
end

test/sets.jl

+13-9
Original file line numberDiff line numberDiff line change
@@ -384,27 +384,30 @@ end
384384
end
385385

386386
@testset "unique" begin
387-
u = unique([1, 1, 2])
387+
u = @inferred(unique([1, 1, 2]))
388388
@test in(1, u)
389389
@test in(2, u)
390390
@test length(u) == 2
391-
@test unique(iseven, [5, 1, 8, 9, 3, 4, 10, 7, 2, 6]) == [5, 8]
392-
@test unique(n -> n % 3, [5, 1, 8, 9, 3, 4, 10, 7, 2, 6]) == [5, 1, 9]
391+
@test @inferred(unique(iseven, [5, 1, 8, 9, 3, 4, 10, 7, 2, 6])) == [5, 8]
392+
@test @inferred(unique(x->x^2, Integer[3, -4, 5, 4])) == Integer[3, -4, 5]
393+
@test @inferred(unique(iseven, Integer[3, -4, 5, 4]; seen=Set{Bool}())) == Integer[3, -4]
394+
@test @inferred(unique(n -> n % 3, [5, 1, 8, 9, 3, 4, 10, 7, 2, 6])) == [5, 1, 9]
393395
end
394396

395397
@testset "issue 20105" begin
396398
@test @inferred(unique(x for x in 1:1)) == [1]
397399
@test unique(x for x in Any[1, 1.0])::Vector{Real} == [1]
398400
@test unique(x for x in Real[1, 1.0])::Vector{Real} == [1]
399-
@test unique(Integer[1, 1, 2])::Vector{Integer} == [1, 2]
401+
@test @inferred(unique(Integer[1, 1, 2]))::Vector{Integer} == [1, 2]
402+
@test unique(x for x in []) isa Vector{Any}
400403
end
401404

402405
@testset "unique!" begin
403406
u = [1,1,3,2,1]
404-
unique!(u)
407+
@inferred(unique!(u))
405408
@test u == [1,3,2]
406-
@test unique!([]) == []
407-
@test unique!(Float64[]) == Float64[]
409+
@test @inferred(unique!([])) == []
410+
@test @inferred(unique!(Float64[])) == Float64[]
408411
u = [1,2,2,3,5,5]
409412
@test unique!(u) === u
410413
@test u == [1,2,3,5]
@@ -434,8 +437,9 @@ end
434437
u = [1,2,5,1,3,2]
435438
@test unique!(x -> x ^ 2, [1, -1, 3, -3, 5, -5]) == [1, 3, 5]
436439
@test unique!(n -> n % 3, [5, 1, 8, 9, 3, 4, 10, 7, 2, 6]) == [5, 1, 9]
437-
@test unique!(iseven, [2, 3, 5, 7, 9]) == [2, 3]
438-
@test unique!(x -> x % 2 == 0 ? :even : :odd, [1, 2, 3, 4, 2, 2, 1]) == [1, 2]
440+
@test @inferred(unique!(iseven, [2, 3, 5, 7, 9])) == [2, 3]
441+
@test @inferred(unique!(x -> x % 2 == 0 ? :even : :odd, [1, 2, 3, 4, 2, 2, 1])) == [1, 2]
442+
@test @inferred(unique!(x -> x % 2 == 0 ? :even : "odd", [1, 2, 3, 4, 2, 2, 1]; seen=Set{Union{Symbol,String}}())) == [1, 2]
439443
end
440444

441445
@testset "allunique" begin

0 commit comments

Comments
 (0)