From 8e29d312c4b0e8efc7a5a3be99558f7fc30d72e0 Mon Sep 17 00:00:00 2001 From: Chengyu HAN Date: Sat, 3 May 2025 13:12:51 +0800 Subject: [PATCH 01/17] doc: add doc and examples for `ncpartitions(n::Int)` --- src/partitions.jl | 40 +++++++++++++++++++++++++++++++++++++--- 1 file changed, 37 insertions(+), 3 deletions(-) diff --git a/src/partitions.jl b/src/partitions.jl index 305e23b..12b890b 100644 --- a/src/partitions.jl +++ b/src/partitions.jl @@ -474,12 +474,46 @@ function integer_partitions(n::Integer) end - -#Noncrossing partitions +# Noncrossing partitions const _cmp = cmp -#Produces noncrossing partitions of length n +""" + ncpartitions(n::Int) + +Generates all noncrossing partitions of a set of `n` elements, +returning them as a `Vector` of partition representations. + +The number of noncrossing partitions of an `n`-element set +is given by the n-th Catalan number `Cn`: +`length(ncpartitions(n)) == catalannum(n)`. + +See also: [`catalannum`](@ref) + +# Examples +```jldoctest +julia> ncpartitions(1) +1-element Vector{Vector{Vector{Int64}}}: + [[1]] + +julia> ncpartitions(3) +5-element Vector{Vector{Vector{Int64}}}: + [[1], [2], [3]] + [[1], [2, 3]] + [[1, 2], [3]] + [[1, 3], [2]] + [[1, 2, 3]] + +julia> catalannum(3) +5 + +julia> length(ncpartitions(6)) == catalannum(6) +true +``` + +# References +- [Noncrossing partition - Wikipedia](https://en.wikipedia.org/wiki/Noncrossing_partition) +""" function ncpartitions(n::Int) partitions = Vector{Vector{Int}}[] _ncpart!(1,n,n,Vector{Int}[], partitions) From dd3d70785255df86fa4e91ce85c109c7bfd15b05 Mon Sep 17 00:00:00 2001 From: Chengyu HAN Date: Sat, 3 May 2025 13:14:36 +0800 Subject: [PATCH 02/17] test: add more tests for `ncpartitions(n::Int)` --- test/partitions.jl | 43 +++++++++++++++++++++++++------------------ 1 file changed, 25 insertions(+), 18 deletions(-) diff --git a/test/partitions.jl b/test/partitions.jl index 4e42ffb..51d9f78 100644 --- a/test/partitions.jl +++ b/test/partitions.jl @@ -35,25 +35,32 @@ @test prevprod([2, 3, 5], 30) == 30 @test prevprod([2, 3, 5], 33) == 32 - # noncrossing partitions - let nc4 = ncpartitions(4) - @test nc4 == Any[ - Any[[1], [2], [3], [4]], - Any[[1], [2], [3, 4]], - Any[[1], [2, 3], [4]], - Any[[1], [2, 4], [3]], - Any[[1], [2, 3, 4]], - Any[[1, 2], [3], [4]], - Any[[1, 2], [3, 4]], - Any[[1, 3], [2], [4]], - Any[[1, 4], [2], [3]], - Any[[1, 4], [2, 3]], - Any[[1, 2, 3], [4]], - Any[[1, 3, 4], [2]], - Any[[1, 2, 4], [3]], - Any[[1, 2, 3, 4]], + @testset "noncrossing partitions" begin + @test ncpartitions(0) == [] + @test ncpartitions(1) == [[[1]]] + @test ncpartitions(2) == [[[1], [2]], [[1, 2]]] + # The 14 noncrossing partitions of a 4-element set ordered in a Hasse diagram + # https://commons.wikimedia.org/wiki/File:Noncrossing_partitions_4;_Hasse.svg + @test ncpartitions(4) == [ + [[1], [2], [3], [4]], + [[1], [2], [3, 4]], + [[1], [2, 3], [4]], + [[1], [2, 4], [3]], + [[1], [2, 3, 4]], + [[1, 2], [3], [4]], + [[1, 2], [3, 4]], + [[1, 3], [2], [4]], + [[1, 4], [2], [3]], + [[1, 4], [2, 3]], + [[1, 2, 3], [4]], + [[1, 3, 4], [2]], + [[1, 2, 4], [3]], + [[1, 2, 3, 4]], ] - @test length(nc4) == catalannum(4) + + for n in 1:8 + @test length(ncpartitions(n)) == catalannum(n) + end end end From 5fd8008477b1c5a5773314d7ced1f8b12e9c8837 Mon Sep 17 00:00:00 2001 From: Chengyu HAN Date: Sat, 3 May 2025 13:54:34 +0800 Subject: [PATCH 03/17] doc: update doc and examples for `integer_partitions(n)` --- src/partitions.jl | 34 +++++++++++++++++++++++++++++++++- 1 file changed, 33 insertions(+), 1 deletion(-) diff --git a/src/partitions.jl b/src/partitions.jl index 12b890b..9df74a2 100644 --- a/src/partitions.jl +++ b/src/partitions.jl @@ -446,11 +446,43 @@ end """ integer_partitions(n) -List the partitions of the integer `n`. +Generates all partitions of the integer `n` as a list of integer arrays, +where each partition represents a way to write `n` as a sum of positive integers. + +See also: [`partitions(n::Integer)`](@ref) !!! note The order of the resulting array is consistent with that produced by the computational discrete algebra software GAP. + +# Examples +```jldoctest +julia> integer_partitions(2) +2-element Vector{Vector{Int64}}: + [1, 1] + [2] + +julia> integer_partitions(3) +3-element Vector{Vector{Int64}}: + [1, 1, 1] + [2, 1] + [3] + +julia> collect(partitions(3)) +3-element Vector{Vector{Int64}}: + [3] + [2, 1] + [1, 1, 1] + +julia> integer_partitions(-1) +ERROR: DomainError with -1: +n must be nonnegative +Stacktrace: +[...] +``` + +# References +- [Integer partition - Wikipedia](https://en.wikipedia.org/wiki/Integer_partition) """ function integer_partitions(n::Integer) if n < 0 From 0bd13b127e95c1533111e9d70d9d568104b02e91 Mon Sep 17 00:00:00 2001 From: Chengyu HAN Date: Sat, 3 May 2025 13:54:46 +0800 Subject: [PATCH 04/17] test: add more tests for `integer_partitions(n)` --- test/partitions.jl | 24 ++++++++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/test/partitions.jl b/test/partitions.jl index 51d9f78..b83682e 100644 --- a/test/partitions.jl +++ b/test/partitions.jl @@ -26,10 +26,26 @@ @test length(collect(partitions('a':'h'))) == length(partitions('a':'h')) @test length(collect(partitions('a':'h', 5))) == length(partitions('a':'h', 5)) - # integer_partitions - @test integer_partitions(0) == [] - @test integer_partitions(5) == Any[[1, 1, 1, 1, 1], [2, 1, 1, 1], [2, 2, 1], [3, 1, 1], [3, 2], [4, 1], [5]] - @test_throws DomainError integer_partitions(-1) + @testset "integer partitions" begin + @test_broken integer_partitions(0) == [[]] + @test integer_partitions(1) == [[1]] + @test integer_partitions(2) == [[1, 1], [2]] + @test integer_partitions(3) == [[1, 1, 1], [2, 1], [3]] + # gap> Partitions( 5 ); + @test integer_partitions(5) == [ + [1, 1, 1, 1, 1], + [2, 1, 1, 1], + [2, 2, 1], + [3, 1, 1], + [3, 2], + [4, 1], + [5] + ] + # integer_partitions <--> partitions(::Integer) + @test Set(integer_partitions(5)) == Set(partitions(5)) + + @test_throws DomainError integer_partitions(-1) + end @test_throws ArgumentError prevprod([2, 3, 5], Int128(typemax(Int)) + 1) @test prevprod([2, 3, 5], 30) == 30 From 763913cae81168248ad46ca26029e4904a3cef46 Mon Sep 17 00:00:00 2001 From: Chengyu HAN Date: Sat, 3 May 2025 14:27:55 +0800 Subject: [PATCH 05/17] doc: update doc and examples for `partitions(n::Integer)` --- src/partitions.jl | 53 ++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 46 insertions(+), 7 deletions(-) diff --git a/src/partitions.jl b/src/partitions.jl index 9df74a2..05c8211 100644 --- a/src/partitions.jl +++ b/src/partitions.jl @@ -8,7 +8,7 @@ export #nextprod, -#integer partitions +# integer partitions struct IntegerPartitions n::Int @@ -24,17 +24,56 @@ function Base.iterate(p::IntegerPartitions, xs = Int[]) end """ - partitions(n) + partitions(n::Integer) -Generate all integer arrays that sum to `n`. Because the number of partitions can be very -large, this function returns an iterator object. Use `collect(partitions(n))` to get an -array of all partitions. The number of partitions to generate can be efficiently computed -using `length(partitions(n))`. +Generate all integer arrays that sum to `n`. + +Because the number of partitions can be very large, +this function returns an iterator object. +Use `collect(partitions(n))` to get an array of all partitions. + +The number of partitions to generate can be efficiently computed using +`length(partitions(n))`. + +See also: +- [`integer_partitions(n::Integer)`](@ref) + for a non-iterator version that returns all partitions as a array +- [`partitions(n::Integer, m::Integer)`](@ref) + for partitions with exactly `m` parts. + +## Examples +```jldoctest +julia> collect(partitions(2)) +2-element Vector{Vector{Int64}}: + [2] + [1, 1] + +julia> collect(partitions(3)) +3-element Vector{Vector{Int64}}: + [3] + [2, 1] + [1, 1, 1] + +julia> integer_partitions(3) +3-element Vector{Vector{Int64}}: + [1, 1, 1] + [2, 1] + [3] + +julia> first(partitions(10)) +1-element Vector{Int64}: + 10 + +julia> length(partitions(10)) +42 +``` + +# References +- [Integer partition - Wikipedia](https://en.wikipedia.org/wiki/Integer_partition) """ partitions(n::Integer) = IntegerPartitions(n) - function nextpartition(n, as) isempty(as) && return Int[n] From 1ec9e622b5ee93901fbd10523a8282946b383e5e Mon Sep 17 00:00:00 2001 From: Chengyu HAN Date: Sat, 3 May 2025 14:30:01 +0800 Subject: [PATCH 06/17] test: add more tests for `partitions(n::Integer)` --- test/partitions.jl | 25 ++++++++++++++++++++----- 1 file changed, 20 insertions(+), 5 deletions(-) diff --git a/test/partitions.jl b/test/partitions.jl index b83682e..e1a8d09 100644 --- a/test/partitions.jl +++ b/test/partitions.jl @@ -1,5 +1,24 @@ @testset "partitions" begin - @test collect(partitions(4)) == Any[[4], [3, 1], [2, 2], [2, 1, 1], [1, 1, 1, 1]] + + @testset "partitions(n::Integer)" begin + @test_broken collect(partitions(0)) == [[]] + @test collect(partitions(1)) == [[1]] + @test collect(partitions(2)) == [[2], [1, 1]] + @test collect(partitions(3)) == [[3], [2, 1], [1, 1, 1]] + @test collect(partitions(4)) == [[4], [3, 1], [2, 2], [2, 1, 1], [1, 1, 1, 1]] + + # Partition function p(n): https://oeis.org/A000041 + @test length(partitions(-1)) == 0 + @test length(partitions(0)) == 1 + @test length(partitions(1)) == 1 + @test length(partitions(40)) == 37338 + @test length(partitions(49)) == 173525 + + # Type stable + @inferred first(partitions(4)) + @test isa(collect(partitions(4)), Vector{Vector{Int}}) + end + @test collect(partitions(8, 3)) == Any[[6, 1, 1], [5, 2, 1], [4, 3, 1], [4, 2, 2], [3, 3, 2]] @test collect(partitions(8, 1)) == Any[[8]] @test collect(partitions(8, 9)) == [] @@ -9,18 +28,14 @@ @test collect(partitions([1, 2, 3, 4], 1)) == Any[Any[[1, 2, 3, 4]]] @test collect(partitions([1, 2, 3, 4], 5)) == [] - @inferred first(partitions(4)) @inferred first(partitions(8, 3)) @inferred first(partitions([1, 2, 3])) @inferred first(partitions([1, 2, 3, 4], 3)) - @test isa(collect(partitions(4)), Vector{Vector{Int}}) @test isa(collect(partitions(8, 3)), Vector{Vector{Int}}) @test isa(collect(partitions([1, 2, 3])), Vector{Vector{Vector{Int}}}) @test isa(collect(partitions([1, 2, 3, 4], 3)), Vector{Vector{Vector{Int}}}) - @test length(partitions(0)) == 1 - @test length(partitions(-1)) == 0 @test length(collect(partitions(30))) == length(partitions(30)) @test length(collect(partitions(90, 4))) == length(partitions(90, 4)) @test length(collect(partitions('a':'h'))) == length(partitions('a':'h')) From 9e5cf7c67e3dc5692a715a47765b87b31c54359c Mon Sep 17 00:00:00 2001 From: Chengyu HAN Date: Sat, 3 May 2025 15:04:19 +0800 Subject: [PATCH 07/17] doc: update doc and examples for `partitions(n::Integer, m::Integer)` --- src/partitions.jl | 45 ++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 40 insertions(+), 5 deletions(-) diff --git a/src/partitions.jl b/src/partitions.jl index 05c8211..e0e10f8 100644 --- a/src/partitions.jl +++ b/src/partitions.jl @@ -136,12 +136,47 @@ Base.length(f::FixedPartitions) = npartitions(f.n,f.m) Base.eltype(f::FixedPartitions) = Vector{Int} """ - partitions(n, m) + partitions(n::Integer, m::Integer) -Generate all arrays of `m` integers that sum to `n`. Because the number of partitions can -be very large, this function returns an iterator object. Use `collect(partitions(n, m))` to -get an array of all partitions. The number of partitions to generate can be efficiently -computed using `length(partitions(n, m))`. +Generate all integer partitions of `n` into exactly `m` parts, that sum to `n`. + +Because the number of partitions can be very large, +this function returns an iterator object. +Use `collect(partitions(n, m))` to get an array of all partitions. + +The number of partitions to generate can be efficiently computed using +`length(partitions(n, m))`. + +See also: [`partitions(n::Integer)`](@ref) + +## Examples +```jldoctest +julia> collect(partitions(4)) +5-element Vector{Vector{Int64}}: + [4] + [3, 1] + [2, 2] + [2, 1, 1] + [1, 1, 1, 1] + +julia> collect(partitions(4, 2)) +2-element Vector{Vector{Int64}}: + [3, 1] + [2, 2] + +julia> collect(partitions(4, 4)) +1-element Vector{Vector{Int64}}: + [1, 1, 1, 1] + +julia> collect(partitions(4, 5)) +Vector{Int64}[] + +julia> partitions(4, 0) +ERROR: DomainError with (4, 0): +n and m must be positive +Stacktrace: +[...] +``` """ partitions(n::Integer, m::Integer) = n >= 1 && m >= 1 ? From e70897157002dabc22d5d47d970d6a8f04e4a21c Mon Sep 17 00:00:00 2001 From: Chengyu HAN Date: Sat, 3 May 2025 15:17:31 +0800 Subject: [PATCH 08/17] test: add more tests for `partitions(n::Integer, m::Integer)` --- test/partitions.jl | 37 ++++++++++++++++++++++++++++++------- 1 file changed, 30 insertions(+), 7 deletions(-) diff --git a/test/partitions.jl b/test/partitions.jl index e1a8d09..a682434 100644 --- a/test/partitions.jl +++ b/test/partitions.jl @@ -13,31 +13,54 @@ @test length(partitions(1)) == 1 @test length(partitions(40)) == 37338 @test length(partitions(49)) == 173525 + @test length(collect(partitions(30))) == length(partitions(30)) # Type stable @inferred first(partitions(4)) @test isa(collect(partitions(4)), Vector{Vector{Int}}) end - @test collect(partitions(8, 3)) == Any[[6, 1, 1], [5, 2, 1], [4, 3, 1], [4, 2, 2], [3, 3, 2]] - @test collect(partitions(8, 1)) == Any[[8]] - @test collect(partitions(8, 9)) == [] + @testset "partitions(n::Integer, m::Integer)" begin + @test collect(partitions(8, 1)) == [[8]] + @test collect(partitions(8, 3)) == [[6, 1, 1], [5, 2, 1], [4, 3, 1], [4, 2, 2], [3, 3, 2]] + @test collect(partitions(8, 8)) == [ones(Int, 8)] + @test collect(partitions(8, 9)) == [] + @test collect(partitions(90, 90)) == [ones(Int, 90)] + @test collect(partitions(90, 91)) == [] + + # legnth + @test length(partitions(8, 1)) == 1 + @test length(partitions(8, 3)) == 5 + @test length(partitions(8, 8)) == 1 + @test length(partitions(8, 9)) == 0 + @test length(partitions(90, 1)) == 1 + # gap> NrPartitions(90, 4); + @test length(partitions(90, 4)) == 5231 + @test length(collect(partitions(90, 4))) == length(partitions(90, 4)) + @test length(partitions(90, 90)) == 1 + + # Type stable + @inferred first(partitions(8, 3)) + @test isa(collect(partitions(8, 3)), Vector{Vector{Int}}) + + # errors + @test_throws DomainError partitions(-1, 1) + @test_throws DomainError partitions(8, 0) + @test_throws DomainError partitions(8, -1) + end + @test collect(partitions([1, 2, 3])) == Any[Any[[1, 2, 3]], Any[[1, 2], [3]], Any[[1, 3], [2]], Any[[1], [2, 3]], Any[[1], [2], [3]]] @test collect(partitions([1, 2, 3, 4], 3)) == Any[Any[[1, 2], [3], [4]], Any[[1, 3], [2], [4]], Any[[1], [2, 3], [4]], Any[[1, 4], [2], [3]], Any[[1], [2, 4], [3]], Any[[1], [2], [3, 4]]] @test collect(partitions([1, 2, 3, 4], 1)) == Any[Any[[1, 2, 3, 4]]] @test collect(partitions([1, 2, 3, 4], 5)) == [] - @inferred first(partitions(8, 3)) @inferred first(partitions([1, 2, 3])) @inferred first(partitions([1, 2, 3, 4], 3)) - @test isa(collect(partitions(8, 3)), Vector{Vector{Int}}) @test isa(collect(partitions([1, 2, 3])), Vector{Vector{Vector{Int}}}) @test isa(collect(partitions([1, 2, 3, 4], 3)), Vector{Vector{Vector{Int}}}) - @test length(collect(partitions(30))) == length(partitions(30)) - @test length(collect(partitions(90, 4))) == length(partitions(90, 4)) @test length(collect(partitions('a':'h'))) == length(partitions('a':'h')) @test length(collect(partitions('a':'h', 5))) == length(partitions('a':'h', 5)) From 36365ae6f36892509b9f9528d27cc3f66717596a Mon Sep 17 00:00:00 2001 From: Chengyu HAN Date: Sat, 3 May 2025 15:46:20 +0800 Subject: [PATCH 09/17] doc: update doc and examples for `partitions(s::AbstractVector)` --- src/partitions.jl | 42 +++++++++++++++++++++++++++++++++++++----- 1 file changed, 37 insertions(+), 5 deletions(-) diff --git a/src/partitions.jl b/src/partitions.jl index e0e10f8..97dd83e 100644 --- a/src/partitions.jl +++ b/src/partitions.jl @@ -250,11 +250,43 @@ Base.eltype(p::SetPartitions) = Vector{Vector{eltype(p.s)}} """ partitions(s::AbstractVector) -Generate all set partitions of the elements of an array `s`, represented as arrays of -arrays. Because the number of partitions can be very large, this function returns an -iterator object. Use `collect(partitions(s))` to get an array of all partitions. The -number of partitions to generate can be efficiently computed using -`length(partitions(s))`. +Generate all set partitions of the elements of an array `s`, +represented as arrays of arrays. + +Because the number of partitions can be very large, +this function returns an iterator object. +Use `collect(partitions(s))` to get an array of all partitions. + +The number of partitions of an `n`-element set +is given by the n-th Bell number `Bn`: +`length(partitions(s)) == catalannum(legnth(s))`. + +# Examples +```jldoctest +julia> collect(partitions([1, 1])) +2-element Vector{Vector{Vector{Int64}}}: + [[1, 1]] + [[1], [1]] + +julia> collect(partitions(-1:-1:-2)) +2-element Vector{Vector{Vector{Int64}}}: + [[-1, -2]] + [[-1], [-2]] + +julia> collect(partitions('a':'c')) +5-element Vector{Vector{Vector{Char}}}: + [['a', 'b', 'c']] + [['a', 'b'], ['c']] + [['a', 'c'], ['b']] + [['a'], ['b', 'c']] + [['a'], ['b'], ['c']] + +julia> length(partitions(1:10)) == bellnum(10) +true +``` + +# References +- [Partition of a set - Wikipedia](https://en.wikipedia.org/wiki/Partition_of_a_set) """ partitions(s::AbstractVector) = SetPartitions(s) From a649db6395294b89f23400b4b2405b92ebe9d926 Mon Sep 17 00:00:00 2001 From: Chengyu HAN Date: Sat, 3 May 2025 15:47:11 +0800 Subject: [PATCH 10/17] test: add more tests for `partitions(s::AbstractVector)` --- test/partitions.jl | 22 +++++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/test/partitions.jl b/test/partitions.jl index a682434..a56e897 100644 --- a/test/partitions.jl +++ b/test/partitions.jl @@ -49,19 +49,31 @@ @test_throws DomainError partitions(8, -1) end - @test collect(partitions([1, 2, 3])) == Any[Any[[1, 2, 3]], Any[[1, 2], [3]], Any[[1, 3], [2]], Any[[1], [2, 3]], Any[[1], [2], [3]]] + @testset "partitions(s::AbstractVector)" begin + @test collect(partitions([1])) == [[[1]]] + @test collect(partitions([1, 2])) == [[[1, 2]], [[1], [2]]] + @test collect(partitions([1, 2, 3])) == [[[1, 2, 3]], [[1, 2], [3]], [[1, 3], [2]], [[1], [2, 3]], [[1], [2], [3]]] + @test collect(partitions(1:3)) == collect(partitions([1, 2, 3])) + @test collect(partitions('a':'b')) == [[['a', 'b']], [['a'], ['b']]] + + # length: https://oeis.org/A000110 + @test length(partitions(1:10)) == 115975 + @test length(partitions(1:20)) == 51724158235372 + @test length(collect(partitions('a':'h'))) == length(partitions('a':'h')) + + # Type stable + @inferred first(partitions([1, 2, 3])) + @test isa(collect(partitions([1, 2, 3])), Vector{Vector{Vector{Int}}}) + end + @test collect(partitions([1, 2, 3, 4], 3)) == Any[Any[[1, 2], [3], [4]], Any[[1, 3], [2], [4]], Any[[1], [2, 3], [4]], Any[[1, 4], [2], [3]], Any[[1], [2, 4], [3]], Any[[1], [2], [3, 4]]] @test collect(partitions([1, 2, 3, 4], 1)) == Any[Any[[1, 2, 3, 4]]] @test collect(partitions([1, 2, 3, 4], 5)) == [] - @inferred first(partitions([1, 2, 3])) @inferred first(partitions([1, 2, 3, 4], 3)) - - @test isa(collect(partitions([1, 2, 3])), Vector{Vector{Vector{Int}}}) @test isa(collect(partitions([1, 2, 3, 4], 3)), Vector{Vector{Vector{Int}}}) - @test length(collect(partitions('a':'h'))) == length(partitions('a':'h')) @test length(collect(partitions('a':'h', 5))) == length(partitions('a':'h', 5)) @testset "integer partitions" begin From 57ece4fc2152e4cf9adb6054aab35a8eceb96ac1 Mon Sep 17 00:00:00 2001 From: Chengyu HAN Date: Sat, 3 May 2025 16:01:39 +0800 Subject: [PATCH 11/17] doc: update doc and examples for `partitions(s::AbstractVector, m::Int)` --- src/partitions.jl | 44 +++++++++++++++++++++++++++++++++++++++----- 1 file changed, 39 insertions(+), 5 deletions(-) diff --git a/src/partitions.jl b/src/partitions.jl index 97dd83e..48315e8 100644 --- a/src/partitions.jl +++ b/src/partitions.jl @@ -363,11 +363,45 @@ Base.eltype(p::FixedSetPartitions) = Vector{Vector{eltype(p.s)}} partitions(s::AbstractVector, m::Int) Generate all set partitions of the elements of an array `s` into exactly `m` subsets, -represented as arrays of arrays. Because the number of partitions can be very large, -this function returns an iterator object. Use `collect(partitions(s, m))` to get -an array of all partitions. The number of partitions into `m` subsets is equal to the -Stirling number of the second kind, and can be efficiently computed using -`length(partitions(s, m))`. +represented as arrays of arrays. + +Because the number of partitions can be very large, +this function returns an iterator object. +Use `collect(partitions(s, m))` to get an array of all partitions. + +The number of partitions into `m` subsets is equal to +the Stirling number of the second kind, +and can be efficiently computed using `length(partitions(s, m))`. + +See also: [`stirlings2(n::Int, k::Int)`](@ref) + +# Examples +```jldoctest +julia> collect(partitions('a':'c', 3)) +1-element Vector{Vector{Vector{Char}}}: + [['a'], ['b'], ['c']] + +julia> collect(partitions([1, 1, 1], 2)) +3-element Vector{Vector{Vector{Int64}}}: + [[1, 1], [1]] + [[1, 1], [1]] + [[1], [1, 1]] + +julia> collect(partitions(1:3, 2)) +3-element Vector{Vector{Vector{Int64}}}: + [[1, 2], [3]] + [[1, 3], [2]] + [[1], [2, 3]] + +julia> stirlings2(3, 2) +3 + +julia> length(partitions(1:10, 3)) == stirlings2(10, 3) +true +``` + +# References +- [Partition of a set - Wikipedia](https://en.wikipedia.org/wiki/Partition_of_a_set) """ partitions(s::AbstractVector, m::Int) = length(s) >= 1 && m >= 1 ? From ffee802fb9cd0c3cb59a0354d34b34e06006a606 Mon Sep 17 00:00:00 2001 From: Chengyu HAN Date: Sat, 3 May 2025 16:01:55 +0800 Subject: [PATCH 12/17] test: add more tests for `partitions(s::AbstractVector, m::Int)` --- test/partitions.jl | 35 +++++++++++++++++++++++++++-------- 1 file changed, 27 insertions(+), 8 deletions(-) diff --git a/test/partitions.jl b/test/partitions.jl index a56e897..e94c8dc 100644 --- a/test/partitions.jl +++ b/test/partitions.jl @@ -66,15 +66,34 @@ @test isa(collect(partitions([1, 2, 3])), Vector{Vector{Vector{Int}}}) end - @test collect(partitions([1, 2, 3, 4], 3)) == Any[Any[[1, 2], [3], [4]], Any[[1, 3], [2], [4]], Any[[1], [2, 3], [4]], - Any[[1, 4], [2], [3]], Any[[1], [2, 4], [3]], Any[[1], [2], [3, 4]]] - @test collect(partitions([1, 2, 3, 4], 1)) == Any[Any[[1, 2, 3, 4]]] - @test collect(partitions([1, 2, 3, 4], 5)) == [] - - @inferred first(partitions([1, 2, 3, 4], 3)) - @test isa(collect(partitions([1, 2, 3, 4], 3)), Vector{Vector{Vector{Int}}}) + @testset "partitions(s::AbstractVector, m::Int)" begin + @test collect(partitions(1:3, 2)) == [ + [[1, 2], [3]], + [[1, 3], [2]], + [[1], [2, 3]], + ] + @test collect(partitions([1, 2, 3, 4], 1)) == [[[1, 2, 3, 4]]] + @test collect(partitions([1, 2, 3, 4], 3)) == [ + [[1, 2], [3], [4]], [[1, 3], [2], [4]], [[1], [2, 3], [4]], + [[1, 4], [2], [3]], [[1], [2, 4], [3]], [[1], [2], [3, 4]] + ] + @test collect(partitions([1, 2, 3, 4], 4)) == [[[1], [2], [3], [4]]] + @test collect(partitions([1, 2, 3, 4], 5)) == [] + + # length: Stirling numbers of the second kind + # https://dlmf.nist.gov/26.8#T2 + @test length(partitions(1:3, 2)) == 3 + @test length(partitions([1, 2, 3, 4], 3)) == 6 + @test length(partitions(1:10, 4)) == 34105 + @test length(partitions(1:10, 5)) == 42525 + @test length(partitions(1:10, 5)) == stirlings2(10, 5) + @test length(partitions(1:10, 6)) == 22827 + @test length(collect(partitions('a':'h', 5))) == length(partitions('a':'h', 5)) - @test length(collect(partitions('a':'h', 5))) == length(partitions('a':'h', 5)) + # Type stable + @inferred first(partitions([1, 2, 3, 4], 3)) + @test isa(collect(partitions([1, 2, 3, 4], 3)), Vector{Vector{Vector{Int}}}) + end @testset "integer partitions" begin @test_broken integer_partitions(0) == [[]] From 0280cc5ade637e8f0573f7abd30aa0ea15ccb097 Mon Sep 17 00:00:00 2001 From: Chengyu HAN Date: Sat, 3 May 2025 16:04:37 +0800 Subject: [PATCH 13/17] doc: add link to `bellnum` --- src/partitions.jl | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/partitions.jl b/src/partitions.jl index 48315e8..e8533ec 100644 --- a/src/partitions.jl +++ b/src/partitions.jl @@ -261,6 +261,8 @@ The number of partitions of an `n`-element set is given by the n-th Bell number `Bn`: `length(partitions(s)) == catalannum(legnth(s))`. +See also: [`bellnum`](@ref) + # Examples ```jldoctest julia> collect(partitions([1, 1])) From 9ee76b5a2278bdc53b33da41db9f45833a3c94af Mon Sep 17 00:00:00 2001 From: Chengyu HAN Date: Sat, 3 May 2025 16:11:00 +0800 Subject: [PATCH 14/17] doc: add doc for `stirlings2` --- src/numbers.jl | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/numbers.jl b/src/numbers.jl index 4039f9f..f1c2bb3 100644 --- a/src/numbers.jl +++ b/src/numbers.jl @@ -151,6 +151,11 @@ function stirlings1(n::Int, k::Int, signed::Bool=false) return (n - 1) * stirlings1(n - 1, k) + stirlings1(n - 1, k - 1) end +""" + stirlings2(n::Int, k::Int) + +Compute the Stirling number of the second kind, `S(n,k)`. +""" function stirlings2(n::Int, k::Int) if n < 0 throw(DomainError(n, "n must be nonnegative")) From a99c9350de81bedcdd23d2425deac8be03afc63c Mon Sep 17 00:00:00 2001 From: Chengyu HAN Date: Sat, 3 May 2025 16:46:11 +0800 Subject: [PATCH 15/17] doc: update doc and examples for `prevprod` --- src/partitions.jl | 30 +++++++++++++++++++++++------- 1 file changed, 23 insertions(+), 7 deletions(-) diff --git a/src/partitions.jl b/src/partitions.jl index e8533ec..c1bac43 100644 --- a/src/partitions.jl +++ b/src/partitions.jl @@ -35,7 +35,7 @@ Use `collect(partitions(n))` to get an array of all partitions. The number of partitions to generate can be efficiently computed using `length(partitions(n))`. -See also: +See also: - [`integer_partitions(n::Integer)`](@ref) for a non-iterator version that returns all partitions as a array - [`partitions(n::Integer, m::Integer)`](@ref) @@ -536,15 +536,31 @@ end """ prevprod(a::Vector{Int}, x) -Previous integer not greater than `x` that can be written as ``\\prod k_i^{p_i}`` for -integers ``p_1``, ``p_2``, etc. +Find the largest integer not greater than `x` +that can be expressed as a product of powers of the elements in `a`. -For integers ``i_1``, ``i_2``, ``i_3``, this is equivalent to finding the largest ``x`` -such that +This function computes the largest value `y ≤ x` that can be written as: +```math +y = \\prod a_i^{n_i} + = a_1^{n_1} a_2^{n_2} \\cdots a_k^{n_k} + \\leq x +``` +where ``n_i`` is a non-negative integer, `k` is the length of Vector `a`. + +# Examples +```jldoctest +julia> prevprod([10], 1000) # 1000 = 10^3 +1000 + +julia> prevprod([2, 5], 30) # 25 = 2^0 * 5^2 +25 -``i_1^{n_1} i_2^{n_2} i_3^{n_3} \\leq x`` +julia> prevprod([2, 3], 100) # 96 = 2^5 * 3^1 +96 -for integers ``n_1``, ``n_2``, ``n_3``. +julia> prevprod([2, 3, 5], 1) # 1 = 2^0 * 3^0 * 5^0 +1 +``` """ function prevprod(a::Vector{Int}, x) if x > typemax(Int) From c6266df865c67ffcdf18ea05d036a9c3cfcd0761 Mon Sep 17 00:00:00 2001 From: Chengyu HAN Date: Sat, 3 May 2025 16:47:06 +0800 Subject: [PATCH 16/17] test: add more tests for `prevprod` --- test/partitions.jl | 21 +++++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/test/partitions.jl b/test/partitions.jl index e94c8dc..fba1eb6 100644 --- a/test/partitions.jl +++ b/test/partitions.jl @@ -60,7 +60,7 @@ @test length(partitions(1:10)) == 115975 @test length(partitions(1:20)) == 51724158235372 @test length(collect(partitions('a':'h'))) == length(partitions('a':'h')) - + # Type stable @inferred first(partitions([1, 2, 3])) @test isa(collect(partitions([1, 2, 3])), Vector{Vector{Vector{Int}}}) @@ -116,9 +116,22 @@ @test_throws DomainError integer_partitions(-1) end - @test_throws ArgumentError prevprod([2, 3, 5], Int128(typemax(Int)) + 1) - @test prevprod([2, 3, 5], 30) == 30 - @test prevprod([2, 3, 5], 33) == 32 + @testset "prevprod" begin + @test prevprod([2, 3, 5], 30) == 30 # 30 = 2 * 3 * 5 + @test prevprod([2, 3, 5], 33) == 32 # 32 = 2^5 + @test prevprod([2, 3, 5, 7], 420) == 420 # 420 = 2^2 * 3 * 5 * 7 + + # prime factor: https://en.wikipedia.org/wiki/Table_of_prime_factors + prime_factors = [2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37] + @test prevprod(prime_factors, 37) == 37 + @test prevprod(prime_factors, 97) == 96 # 96 = 2^5 * 3 + @test prevprod(prime_factors, 149) == 148 # 148 = 2^2 * 37 + @test prevprod(prime_factors, 911) == 910 # 910 = 2 * 5 * 7 * 13 + @test prevprod(prime_factors, 4999) == 4998 # 4998 = 2 * 3 * 7^2 * 17 + + # errors + @test_throws ArgumentError prevprod([2, 3, 5], Int128(typemax(Int)) + 1) + end @testset "noncrossing partitions" begin @test ncpartitions(0) == [] From 8dbace9bb0ee3c95410ca371703cd028092a69f0 Mon Sep 17 00:00:00 2001 From: Chengyu Han Date: Wed, 7 May 2025 15:54:51 +0800 Subject: [PATCH 17/17] Update src/partitions.jl --- src/partitions.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/partitions.jl b/src/partitions.jl index c1bac43..36ffb79 100644 --- a/src/partitions.jl +++ b/src/partitions.jl @@ -259,7 +259,7 @@ Use `collect(partitions(s))` to get an array of all partitions. The number of partitions of an `n`-element set is given by the n-th Bell number `Bn`: -`length(partitions(s)) == catalannum(legnth(s))`. +`length(partitions(s)) == bellnum(length(s))`. See also: [`bellnum`](@ref)