Skip to content
Open
Show file tree
Hide file tree
Changes from all 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
Original file line number Diff line number Diff line change
Expand Up @@ -69,8 +69,9 @@ julia> C[1]
```


## Generating
## Generating and counting

```@docs
combinations
number_of_combinations
```
30 changes: 23 additions & 7 deletions src/Combinatorics/EnumerativeCombinatorics/combinations.jl
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
@doc raw"""
combinations(n::IntegerUnion, k::IntegerUnion)
combinations(n::IntegerUnion, k::IntegerUnion; inplace::Bool=false)

Return an iterator over all $k$-combinations of ${1,...,n}$, produced in
lexicographically ascending order.

If `inplace` is `true`, the elements of the iterator may share their memory. This
means that an element returned by the iterator may be overwritten 'in place' in
the next iteration step. This may result in significantly fewer memory allocations.

# Examples

```jldoctest
Expand All @@ -18,7 +22,7 @@ julia> collect(C)
[2, 3, 4]
```
"""
combinations(n::T, k::T) where T<:IntegerUnion = Combinations(Base.oneto(n), n, k)
combinations(n::T, k::T; inplace::Bool = false) where T<:IntegerUnion = Combinations(Base.oneto(n), n, k, inplace)

@doc raw"""
combinations(v::AbstractVector, k::IntegerUnion)
Expand All @@ -42,8 +46,6 @@ julia> collect(C)
"""
combinations(v::AbstractVector, k::IntegerUnion) = Combinations(v, k)

Combinations(v::AbstractArray, k::T) where {T<:IntegerUnion} = Combinations(v, T(length(v)), k)

@inline function Base.iterate(C::Combinations{<:AbstractVector{T}, U}, state::Vector{U} = U[min(C.k - 1, i) for i in Base.oneto(C.k)]) where {T, U<:IntegerUnion}
if is_zero(C.k) # special case to generate 1 result for k = 0
if isempty(state)
Expand All @@ -64,13 +66,27 @@ Combinations(v::AbstractArray, k::T) where {T<:IntegerUnion} = Combinations(v, T
if state[1] > C.n - C.k + 1
return nothing
end
return Combination{T}(C.v[state]), state
c = C.inplace ? Combination{T}(state) : Combination{T}(C.v[state])
return c, state
end

Base.length(C::Combinations) = binomial(Int(C.n), Int(C.k))

Base.eltype(::Type{<:Combinations{T}}) where {T} = Combination{eltype(T)}

@doc raw"""
number_of_multicombinations(n::IntegerUnion, k::IntegerUnion)

Return the number of $k$-combinations of ${1, \ldots, n}$.
If `n < 0` or `k < 0`, return `0`.
"""
function number_of_combinations(n::IntegerUnion, k::IntegerUnion)
if n < 0 || k < 0
return ZZ(0)
end
return binomial(ZZ(n), ZZ(k))
end

Base.length(C::Combinations) = BigInt(number_of_combinations(C.n, C.k))

function Base.show(io::IO, C::Combinations{<:Base.OneTo})
print(io, "Iterator over the ", C.k, "-combinations of ", 1:C.n)
end
Expand Down
31 changes: 24 additions & 7 deletions src/Combinatorics/EnumerativeCombinatorics/multicombinations.jl
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
@doc raw"""
multicombinations(n::IntegerUnion, k::IntegerUnion)
multicombinations(n::IntegerUnion, k::IntegerUnion; inplace::Bool=false)

Return an iterator over all $k$-combinations of ${1,...,n}$ with repetition,
produced in lexicographically ascending order.

If `inplace` is `true`, the elements of the iterator may share their memory. This
means that an element returned by the iterator may be overwritten 'in place' in
the next iteration step. This may result in significantly fewer memory allocations.

# Examples

```jldoctest
Expand All @@ -24,7 +28,7 @@ julia> collect(C)
[4, 4]
```
"""
multicombinations(n::T, k::T) where T<:IntegerUnion = MultiCombinations(Base.oneto(n), n, k)
multicombinations(n::T, k::T; inplace::Bool = false) where T<:IntegerUnion = MultiCombinations(Base.oneto(n), n, k, inplace)


@doc raw"""
Expand All @@ -47,8 +51,6 @@ julia> collect(multicombinations(['a', 'b', 'c'], 2))
"""
multicombinations(v::AbstractVector, k::IntegerUnion) = MultiCombinations(v, k)

MultiCombinations(v::AbstractArray, k::T) where {T<:IntegerUnion} = MultiCombinations(v, T(length(v)), k)


@inline function Base.iterate(C::MultiCombinations{<:AbstractVector{T}, U}, state::Vector{U} = C.k == 0 ? U[] : (s = ones(U, C.k); s[Int(C.k)] = U(0); s)) where {T, U<:IntegerUnion}
if is_zero(C.k) # special case to generate 1 result for k = 0
Expand All @@ -70,13 +72,28 @@ MultiCombinations(v::AbstractArray, k::T) where {T<:IntegerUnion} = MultiCombina
if state[1] > C.n
return nothing
end
return Combination{T}(C.v[state]), state
end

Base.length(C::MultiCombinations) = binomial(Int(C.n)+Int(C.k)-1, Int(C.k))
c = C.inplace ? Combination{T}(state) : Combination{T}(C.v[state])
return c, state
end

Base.eltype(::Type{<:MultiCombinations{T}}) where {T} = Combination{eltype(T)}

@doc raw"""
number_of_multicombinations(n::IntegerUnion, k::IntegerUnion)

Return the number of $k$-combinations of ${1, \ldots, n}$.
If `n < 0` or `k < 0`, return `0`.
"""
function number_of_multicombinations(n::IntegerUnion, k::IntegerUnion)
if n < 0 || k < 0
return ZZ(0)
end
return binomial(ZZ(n) + ZZ(k) - 1, ZZ(k))
end

Base.length(C::MultiCombinations) = BigInt(number_of_multicombinations(C.n, C.k))

function Base.show(io::IO, C::MultiCombinations{<:Base.OneTo})
print(io, "Iterator over the ", C.k, "-combinations of ", 1:C.n, " with repetition")
end
Expand Down
27 changes: 24 additions & 3 deletions src/Combinatorics/EnumerativeCombinatorics/types.jl
Original file line number Diff line number Diff line change
Expand Up @@ -401,24 +401,45 @@ end
#
################################################################################

struct Combination{T} <: AbstractVector{T}
v::Vector{T}
end

# Iterator type: all combinations of k elements from the vector v
struct Combinations{T, U<:IntegerUnion}
v::T
n::U
k::U
end

struct Combination{T} <: AbstractVector{T}
v::Vector{T}
inplace::Bool # Whether all generated combinations share the same array in
# memory

function Combinations(v::T, n::U, k::U, inplace::Bool = false) where {T, U<:IntegerUnion}
return new{T,U}(v, n, k, inplace)
end
end


Combinations(v::AbstractArray, k::T) where {T<:IntegerUnion} = Combinations(v, T(length(v)), k)

################################################################################
#
# Multicombination(s)
#
################################################################################

# Iterator type: all combinations of k elements from the vector v with repetition
struct MultiCombinations{T, U<:IntegerUnion}
v::T
n::U
k::U

inplace::Bool # Whether all generated combinations share the same array in
# memory

function MultiCombinations(v::T, n::U, k::U, inplace::Bool = false) where {T, U<:IntegerUnion}
return new{T,U}(v, n, k, inplace)
end
end

MultiCombinations(v::AbstractArray, k::T) where {T<:IntegerUnion} = MultiCombinations(v, T(length(v)), k)
Original file line number Diff line number Diff line change
Expand Up @@ -73,12 +73,16 @@ end
################################################################################

@doc raw"""
weak_compositions(n::IntegerUnion, k::IntegerUnion)
weak_compositions(n::IntegerUnion, k::IntegerUnion; inplace::Bool=false)

Return an iterator over all weak compositions of a non-negative integer `n` into
`k` parts, produced in lexicographically *descending* order.
Using a smaller integer type for `n` (e.g. `Int8`) may increase performance.

If `inplace` is `true`, the elements of the iterator may share their memory. This
means that an element returned by the iterator may be overwritten 'in place' in
the next iteration step. This may result in significantly fewer memory allocations.

By a weak composition of `n` into `k` parts we mean a sequence of `k` non-negative
integers whose sum is `n`.

Expand Down
2 changes: 2 additions & 0 deletions src/exports.jl
Original file line number Diff line number Diff line change
Expand Up @@ -1338,6 +1338,7 @@ export normalized_volume
export normalizer
export nullity
export number_of_atlas_groups
export number_of_combinations
export number_of_complement_equations
export number_of_compositions
export number_of_conjugacy_classes, has_number_of_conjugacy_classes, set_number_of_conjugacy_classes
Expand All @@ -1346,6 +1347,7 @@ export number_of_fixed_points
export number_of_generators
export number_of_groups_with_class_number, has_number_of_groups_with_class_number
export number_of_moved_points, has_number_of_moved_points, set_number_of_moved_points
export number_of_multicombinations
export number_of_multipartitions
export number_of_partitions
export number_of_patches
Expand Down
6 changes: 6 additions & 0 deletions test/Combinatorics/EnumerativeCombinatorics/combinations.jl
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,12 @@

@test collect(combinations(0, 0)) == [Int[]]

@testset "inplace iteration" begin
Ci = combinations(5,3, inplace=true)
Cf = combinations(5,3)
@test all(splat(==), zip(Ci, Cf))
end

@testset "ranking/unranking" begin
@test Oscar.combination(0,0,1) == Combination([])
@test Oscar.combination(3,3,1) == Combination(collect(1:3))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -82,4 +82,10 @@
@test collect(multicombinations(0, 0)) == [Int[]]
@test collect(multicombinations(0, 1)) == []

@testset "inplace iteration" begin
Ci = multicombinations(5,3, inplace=true)
Cf = multicombinations(5,3)
@test all(splat(==), zip(Ci, Cf))
end

end
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,12 @@
@test length(weak_compositions(T(0), T(3))) == 1
@test @inferred collect(weak_compositions(T(0), T(3))) == [weak_composition(T[0, 0, 0])]

@testset "inplace iteration" begin
Ci = weak_compositions(T(5),T(3), inplace=true)
Cf = weak_compositions(T(5),T(3))
@test all(splat(==), zip(Ci, Cf))
end

l = @inferred collect(weak_compositions(T(5), T(3)))
@test length(l) == 21
@test all(x -> sum(x) == 5, l)
Expand Down
Loading