Skip to content

Commit f917260

Browse files
committed
Implement FinBijectionCycles based on Bijections as wrapper types
1 parent b467b58 commit f917260

File tree

4 files changed

+418
-6
lines changed

4 files changed

+418
-6
lines changed

src/categorical_algebra/FinSets.jl

Lines changed: 167 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ module FinSets
44
export FinSet, FinFunction, FinDomFunction, TabularSet, TabularLimit,
55
force, is_indexed, preimage, VarFunction, LooseVarFunction,
66
JoinAlgorithm, SmartJoin, NestedLoopJoin, SortMergeJoin, HashJoin,
7-
SubFinSet, SubOpBoolean, is_monic, is_epic
7+
SubFinSet, SubOpBoolean, is_monic, is_epic, FinBijection
88

99
using StructEquality
1010
using DataStructures: OrderedDict, IntDisjointSets, union!, find_root!
@@ -25,7 +25,8 @@ import ..FinCats: force, ob_generators, hom_generators, ob_generator,
2525
using ..FinCats: dicttype
2626
import ..Limits: limit, colimit, universal, BipartiteColimit
2727
import ..Subobjects: Subobject
28-
using ..Sets: IdentityFunction, SetFunctionCallable
28+
using ..Sets: IdentityFunction, SetFunctionCallable, AbsBijectionWrap,
29+
BijectionBimap, BijectionThinWrap, unwrap
2930

3031
# Finite sets
3132
#############
@@ -459,12 +460,16 @@ is_indexed(f::SetFunction) = false
459460
is_indexed(f::IdentityFunction) = true
460461
is_indexed(f::IndexedFinDomFunctionVector) = true
461462
is_indexed(f::FinDomFunctionVector{T,<:AbstractRange{T}}) where T = true
463+
is_indexed(f::BijectionThinWrap) = is_indexed(f.func)
464+
is_indexed(f::BijectionBimap) = true
462465

463466
""" The preimage (inverse image) of the value y in the codomain.
464467
"""
465468
preimage(f::IdentityFunction, y) = SVector(y)
466469
preimage(f::FinDomFunction, y) = [ x for x in dom(f) if f(x) == y ]
467470
preimage(f::IndexedFinDomFunctionVector, y) = get_preimage_index(f.index, y)
471+
preimage(f::BijectionThinWrap, y) = preimage(unwrap(f), y)
472+
preimage(f::BijectionBimap, y) = f.inv(y)
468473

469474
@inline get_preimage_index(index::AbstractDict, y) = get(index, y, 1:0)
470475
@inline get_preimage_index(index::AbstractVector, y) = index[y]
@@ -571,6 +576,166 @@ Sets.do_compose(f::FinFunctionDict{K,D}, g::FinDomFunctionDict) where {K,D} =
571576
FinDomFunctionDict(dicttype(D)(x => g.func[y] for (x,y) in pairs(f.func)),
572577
codom(g))
573578

579+
# Category of finite sets and bijections
580+
########################################
581+
582+
""" Bijection between finite sets.
583+
"""
584+
const FinBijection{S, S′, Dom <: FinSet{S}, Codom <: FinSet{S′}} =
585+
Bijection{Dom, Codom}
586+
587+
FinBijection(f, args...) = FinBijection(f, (FinSet(a) for a in args)...)
588+
FinBijection(f::Function, dom::FinSet, codom::FinSet) =
589+
BijectionThinWrap(SetFunction(f, dom, codom))
590+
FinBijection(f::FinFunction) = BijectionThinWrap(f)
591+
FinBijection(f::FinFunction, g::FinFunction) = BijectionBimap(unwrap(f), unwrap(g))
592+
FinBijection(f::AbstractVector) =
593+
BijectionThinWrap(FinDomFunction(f, FinSet(Set(f))))
594+
FinBijection(f::AbstractVector, a, args...) =
595+
BijectionThinWrap(FinDomFunction(f, FinSet(a), args...))
596+
FinBijection(f::AbstractDict, args...) =
597+
BijectionThinWrap(FinFunction(f, args...))
598+
599+
function FinBijection(f::Union{AbstractDict{K,Int},AbstractVector{Int}}) where K
600+
function minandmax(p::Tuple{<:Number, <:Number}, n::Int)::Tuple{Int,Int}
601+
(Int(min(p[1], n)), Int(max(p[2], n)))
602+
end
603+
minval, maxval = reduce(minandmax, values(f), init=(Inf, -Inf))
604+
len = length(f)
605+
cod = minval == 1 && maxval == len ? FinSet(len) : Set(v)
606+
BijectionThinWrap(FinFunction(f, cod))
607+
end
608+
609+
Sets.show_type_constructor(io::IO, ::Type{<:FinBijection}) =
610+
print(io, "FinBijection")
611+
612+
""" Abstract (alias) type for bijections on finite sets which are implemented
613+
by wrapping another `FinFunction` object.
614+
"""
615+
const FinBijectionWrap{S,S′,Dom<:FinSet{S},Codom<:FinSet{S′},
616+
F<:FinFunction{S,S′,Dom,Codom}} = AbsBijectionWrap{Dom, Codom, F}
617+
618+
force(f::FinBijectionWrap) = FinBijection(force(unwrap(f)))
619+
620+
""" Alias for all `FinBijection`s that wrap `Vector`s.
621+
"""
622+
const FinBijectionVector = Union{
623+
AbsBijectionWrap{
624+
FinSetInt, Codom, FinFunctionVector{S,T,V,Codom}
625+
} where {S, T, V<:AbstractVector{T}, Codom<:FinSet{S,T}},
626+
AbsBijectionWrap{
627+
FinSetInt, FinSetInt, IndexedFinFunctionVector{V,Index}
628+
} where {V<:AbstractVector{Int}, Index},
629+
}
630+
631+
force(f::FinBijectionVector) = f
632+
633+
Sets.do_compose(f::FinBijectionVector, g::FinBijectionVector) =
634+
BijectionThinWrap(Sets.do_compose(unwrap(f), unwrap(g)))
635+
636+
""" Alias for all `FinBijection`s that wrap `Dict`s.
637+
"""
638+
const FinBijectionDict{K,D<:AbstractDict{K},S,Codom<:FinSet{S}} =
639+
AbsBijectionWrap{
640+
FinSetCollection{Base.KeySet{K,D}}, Codom, FinFunctionDict{K,D,S,Codom}
641+
} where {K, D<:AbstractDict{K}, S, Codom<:FinSet{S}}
642+
643+
force(f::FinBijectionDict) = f
644+
645+
Sets.do_compose(f::FinBijectionDict, g::FinBijectionDict) =
646+
BijectionThinWrap(Sets.do_compose(unwrap(f), unwrap(g)))
647+
648+
function Sets.do_inv(f::FinBijection{S,S′,Dom,Codom}) where
649+
{S,S′,T,T′,Dom<:FinSet{S,T},Codom<:FinSet{S′,T′}}
650+
domain, cod = dom(f), codom(f)
651+
func = S′ == Int ? Vector{T}(undef, length(cod)) : Dict{T′,T}()
652+
for x in domain
653+
func[f(x)] = x
654+
end
655+
FinDomFunction(func, domain)
656+
end
657+
658+
""" Finite bijection whose form in cycle notation is known.
659+
660+
Computing a bijection's cycles from its function requires the same computations
661+
as computing the inverse. Furthermore, the inverse of a function can be computed
662+
from its cycles just as easily as the function itself. This type is designed
663+
with these facts in mind.
664+
"""
665+
struct FinBijectionCycles{S,T,Dom<:FinSet{S,T},F<:FinFunction{S,S,Dom,Dom},
666+
G<:FinFunction{S,S,Dom,Dom}} <: AbsBijectionWrap{Dom,Dom,F}
667+
func::BijectionBimap{Dom,Dom,F,G}
668+
cycles::Vector{Vector{T}}
669+
end
670+
671+
function FinBijectionCycles(f::FinBijection{S,S,Dom,Dom}) where
672+
{S,T,Dom<:FinSet{S,T}}
673+
domain = dom(f)
674+
cycles = Vector{T}[]
675+
if S == Int
676+
invfunc, used = Vector{Int}(undef, length(domain)), falses(length(domain))
677+
else
678+
invfunc, used = Dict{T,T}(), Dict(k=>false for k in domain)
679+
end
680+
# Developer's note: this loop recapitulates the approach of the extant
681+
# function `Permutations.cycles`, with the difference that it ignores
682+
# trivial cycles and interleaves the computation of the inverse
683+
for i in domain
684+
if used[i]; continue end
685+
used[i] = true
686+
j = f(i)
687+
invfunc[j] = i
688+
if j != i
689+
cycle = [i]
690+
while true
691+
push!(cycle, j)
692+
used[j] = true
693+
k = j
694+
j = f(j)
695+
invfunc[j] = k
696+
if j == i; break end
697+
end
698+
push!(cycles, cycle)
699+
end
700+
end
701+
bimap = BijectionBimap(f, FinDomFunction(invfunc, dom(f)))
702+
FinBijectionCycles(bimap, cycles)
703+
end
704+
705+
function FinBijectionCycles(f::AbstractVector{<:AbstractVector{T}},
706+
dom::FinSet{S,T}) where {S,T}
707+
domlen = length(dom)
708+
if S == Int
709+
func, invfunc = Vector{Int}(undef, domlen), Vector{Int}(undef, domlen)
710+
else
711+
func, invfunc = Dict{T,T}(), Dict{T,T}()
712+
end
713+
lengths = map(length, f)
714+
for (i, cycle) in enumerate(f)
715+
len = lengths[i]
716+
lenmod = n -> mod(n, len)
717+
for (j, x) in enumerate(cycle)
718+
func[x], invfunc[x] = cycle[lenmod(j+1)], cycle[lenmod(j-1)]
719+
end
720+
end
721+
bimap = FinBijection(FinDomFunction(func, dom), FinDomFunction(invfunc, dom))
722+
FinBijectionCycles(bimap, f)
723+
end
724+
725+
Base.:(==)(f::FinBijectionCycles, g::FinBijectionCycles) = f.func == g.func
726+
Base.hash(f::FinBijectionCycles) = hash(f.func)
727+
728+
Sets.compose_inv(f::FinBijectionCycles, g::SetFunction) = compose_inv(f.func, g)
729+
Sets.compose_inv(f::SetFunction, g::FinBijectionCycles) = compose_inv(f, g.func)
730+
Sets.compose_inv(f::FinBijectionCycles, g::FinBijectionCycles) =
731+
compose_inv(f.func, g.func)
732+
733+
Base.inv(f::FinBijectionCycles) =
734+
FinBijectionCycles(inv(f.func), [reverse(c) for c in f.cycles])
735+
736+
is_indexed(f::FinBijectionCycles) = true
737+
preimage(f::FinBijectionCycles, y) = preimage(f.func, y)
738+
574739
# Limits
575740
########
576741

0 commit comments

Comments
 (0)