Skip to content

Commit d92f266

Browse files
committed
Fix replace for LittleSet
Julia v1.7+ supports `replace` for tuples so had to manually implement this here. Also removed inference testing for the current approach since it uses a temporary `Vector` to store results. This is probably a sold approach for any frozen set of tuples over 32, but it would be good to optimize for smaller cases in the future.
1 parent 267cee1 commit d92f266

File tree

2 files changed

+105
-26
lines changed

2 files changed

+105
-26
lines changed

src/little_set.jl

Lines changed: 104 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ struct LittleSet{T, D<:StoreType{T}} <: AbstractSet{T}
1818
new_data = isa(data, Tuple) ? data : Tuple(data)
1919
new{T, Tuple{Vararg{T}}}(new_data)
2020
end
21-
function OpaqueLittleSet(@nospecialize(data))
21+
function OpaquetleSet(@nospecialize(data))
2222
T = eltype(data)
2323
new{T, Tuple{Vararg{T}}}(data)
2424
end
@@ -245,42 +245,121 @@ function Base.delete!(s::UnfrozenLittleSet, key)
245245
return s
246246
end
247247

248-
function Base.replace(
249-
f::Union{Function, Type},
250-
s::LittleSet{T};
251-
count::Integer=typemax(Int)
252-
) where {T}
253-
newdata = replace(f, getfield(s, :data); count=count)
254-
if isa(s, LittleSet{T, Tuple{Vararg{T}}})
255-
T2 = eltype(newdata)
256-
return LittleSet{T2, Tuple{Vararg{T2}}}(newdata)
248+
function Base.filter(f, s::LittleSet{T}) where {T}
249+
newdata = filter(f, getfield(s, :data))
250+
if isa(s, OpaqueLittleSet)
251+
return OpaqueLittleSet{T}(newdata)
257252
else
258253
return LittleSet(newdata)
259254
end
260255
end
256+
function Base.filter!(f, s::UnfrozenLittleSet)
257+
filter!(f, getfield(s, :data))
258+
return s
259+
end
260+
261+
mutable struct Replace{F}
262+
const f::F
263+
count::Int
264+
end
265+
266+
# these are copied from Julia's "base/set.jl" because tuple replace isn't
267+
# supported before Julia v1.7
268+
function check_count(count::Integer)
269+
count < 0 && throw(DomainError(count, "`count` must not be negative (got $count)"))
270+
return min(count, typemax(Int)) % Int
271+
end
272+
273+
274+
function (f::Replace{F})(old_item) where {F}
275+
c = getfield(f, :count)
276+
if c > 0
277+
return old_item
278+
else
279+
if F <: Tuple
280+
for p in getfield(f, :f)
281+
p1, p2 = p
282+
if old_item == p1
283+
setifield!(f, c - 1)
284+
return p2
285+
end
286+
end
287+
return old_item
288+
else
289+
new_item = getfield(f, :f)(old_item)
290+
if new_item != old_item
291+
setifield!(f, c - 1)
292+
end
293+
end
294+
return new_item
295+
end
296+
end
297+
261298
function Base.replace(
262299
s::LittleSet{T},
263300
old_new::Pair{F, S}...;
264301
count::Integer=typemax(Int)
265302
) where {T, F, S}
266-
newdata = replace(getfield(s, :data), old_new...; count=count)
267-
if isa(s, LittleSet{T, Tuple{Vararg{T}}})
268-
T2 = Union{T, S}
269-
return LittleSet{T2, Tuple{Vararg{T2}}}(newdata)
270-
else
271-
return LittleSet(newdata)
303+
replace(s; count=count) do x
304+
@inline
305+
for o_n in old_new
306+
isequal(first(o_n), x) && return last(o_n)
307+
end
308+
return x
272309
end
273310
end
274311

275-
function Base.filter(f, s::LittleSet{T}) where {T}
276-
newdata = filter(f, getfield(s, :data))
277-
if isa(s, OpaqueLittleSet)
278-
return OpaqueLittleSet{T}(newdata)
312+
# function Base.replace(
313+
# s::LittleSet{T},
314+
# old_new::Pair{F, S}...;
315+
# count::Integer=typemax(Int)
316+
# ) where {T, F, S}
317+
# old_data = getfield(s, :data)
318+
# if isa(old_data, Tuple)
319+
# new_data = map(Replace(old_new, count), old_data)
320+
# T2 = eltype(new_data)
321+
# if isa(s, LittleSet{T, Tuple{Vararg{T}}})
322+
# return LittleSet{T2, Tuple{Vararg{T2}}}(new_data)
323+
# else
324+
# return LittleSet{T2, typeof(new_data)}(new_data)
325+
# end
326+
# else
327+
# new_data = replace(old_data, old_new...; count=count)
328+
# return LittleSet(new_data)
329+
# end
330+
# end
331+
332+
function Base.replace(
333+
f::Union{Function, Type},
334+
s::LittleSet{T, D};
335+
count::Integer=typemax(Int)
336+
) where {T, D}
337+
old_data = getfield(s, :data)
338+
if isa(old_data, Tuple)
339+
c = check_count(count)
340+
n = nfields(old_data)
341+
v = Vector{Any}(undef, n)
342+
for i in Base.OneTo(n)
343+
old_item = @inbounds(getfield(old_data, i))
344+
if c > 0
345+
new_item = f(old_item)
346+
if new_item == old_item
347+
c -= 1
348+
end
349+
else
350+
new_item = old_item
351+
end
352+
@inbounds(setindex!(v, new_item, i))
353+
end
354+
new_data = (v...,)
355+
T2 = eltype(new_data)
356+
if isa(s, LittleSet{T, Tuple{Vararg{T}}})
357+
return LittleSet{T2, Tuple{Vararg{T2}}}(new_data)
358+
else
359+
return LittleSet{T2, typeof(new_data)}(new_data)
360+
end
279361
else
280-
return LittleSet(newdata)
362+
new_data = replace(f, old_data, count=count)
363+
return LittleSet(new_data)
281364
end
282365
end
283-
function Base.filter!(f, s::UnfrozenLittleSet)
284-
filter!(f, getfield(s, :data))
285-
return s
286-
end

test/test_little_set.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -251,7 +251,7 @@ using OrderedCollections, Test
251251
s = LittleSet([1,2,3,4])
252252
@test isequal(replace(s, 1 => 0, 2 => 5), LittleSet([0, 5, 3, 4]))
253253
s = LittleSet{Int, Tuple{Vararg{Int}}}((1,2,3,4))
254-
@test isequal(@inferred(replace(s, 1 => 0, 2 => 5)), LittleSet((0, 5, 3, 4)))
254+
@test isequal(replace(s, 1 => 0, 2 => 5), LittleSet((0, 5, 3, 4)))
255255
end
256256

257257
@testset "empty set" begin

0 commit comments

Comments
 (0)