├── REQUIRE
├── test
├── runtests.jl
├── youngdiagrams.jl
├── numbers.jl
├── factorials.jl
├── partitions.jl
├── combinations.jl
└── permutations.jl
├── .travis.yml
├── src
├── Combinatorics.jl
├── factorials.jl
├── numbers.jl
├── youngdiagrams.jl
├── permutations.jl
├── combinations.jl
└── partitions.jl
├── LICENSE.md
└── README.md
/REQUIRE:
--------------------------------------------------------------------------------
1 | julia 0.5-
2 | Polynomials
3 | Iterators
4 |
--------------------------------------------------------------------------------
/test/runtests.jl:
--------------------------------------------------------------------------------
1 | using Combinatorics
2 | using Base.Test
3 |
4 | include("numbers.jl")
5 | include("factorials.jl")
6 | include("combinations.jl")
7 | include("permutations.jl")
8 | include("partitions.jl")
9 | include("youngdiagrams.jl")
10 |
11 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: julia
2 | os:
3 | - linux
4 | julia:
5 | - 0.4
6 | - nightly
7 | sudo: false
8 | notifications:
9 | email: false
10 | script:
11 | - if [[ -a .git/shallow ]]; then git fetch --unshallow; fi
12 | - julia -e 'Pkg.clone(pwd())'
13 | - julia -e 'Pkg.test("Combinatorics", coverage=true)'
14 | after_success:
15 | - julia -e 'Pkg.add("Coverage"); cd(Pkg.dir("Combinatorics")); using Coverage; Coveralls.submit(Coveralls.process_folder()); Codecov.submit(process_folder())'
16 |
17 |
--------------------------------------------------------------------------------
/test/youngdiagrams.jl:
--------------------------------------------------------------------------------
1 | using Combinatorics
2 | using Base.Test
3 |
4 | @test ([5,4,2,2]\[2,1]) == ([5, 4, 2, 2],[2, 1])
5 |
6 | @test leglength([5,4,2,2], [2,1]) == leglength(([5, 4, 2, 2],[2, 1])) == 3
7 | @test leglength([1], [1]) == -1
8 | @test leglength(Int[], Int[]) == -1
9 |
10 | @test isrimhook([4,3,2], [2,2,2])
11 | @test !isrimhook([4,3,2], [2,2,1])
12 | @test !isrimhook([4,3,2], [1,1])
13 |
14 | let λ = [5,4,2,1]
15 | @test partitionsequence(λ) == [1, 0, 1, 0, 1, 1, 0, 1, 0]
16 | @test character(λ, [4,3,2,2,1]) == 0
17 | end
18 | @test character([1], [1]) == 1
19 |
20 |
--------------------------------------------------------------------------------
/src/Combinatorics.jl:
--------------------------------------------------------------------------------
1 | module Combinatorics
2 |
3 | using Compat, Polynomials, Iterators
4 |
5 | import Base: start, next, done, length, eltype
6 |
7 | #These 8 functions were removed from Julia 0.5 as part of JuliaLang/julia#13897,
8 | #so check if it's necessary to import them to overload the stub methods left in
9 | #Base.
10 | if isdefined(Base, :combinations)
11 | import Base: combinations, partitions, prevprod, levicivita, nthperm,
12 | nthperm!, parity, permutations
13 | end
14 |
15 | include("numbers.jl")
16 | include("factorials.jl")
17 | include("combinations.jl")
18 | include("permutations.jl")
19 | include("partitions.jl")
20 | include("youngdiagrams.jl")
21 |
22 | end #module
23 |
--------------------------------------------------------------------------------
/test/numbers.jl:
--------------------------------------------------------------------------------
1 | using Combinatorics
2 | using Base.Test
3 |
4 | # catalan
5 | @test catalannum(5) == 42
6 | @test catalannum(30) == parse(BigInt,"3814986502092304")
7 | @test_throws DomainError catalannum(-1)
8 |
9 | # fibonacci
10 | @test fibonaccinum(5) == 5
11 | @test fibonaccinum(101) == parse(BigInt,"573147844013817084101")
12 | @test_throws DomainError fibonaccinum(-1)
13 |
14 | # lassalle
15 | @test lassallenum(14) == parse(BigInt,"270316008395632253340")
16 |
17 | # legendresymbol
18 | @test legendresymbol(1001,9907) == jacobisymbol(1001,9907) == -1
19 |
20 | # lucas
21 | @test lucasnum(10) == 123
22 | @test lucasnum(100) == parse(BigInt,"792070839848372253127")
23 | @test_throws DomainError lucasnum(-1)
24 |
25 | # stirlings1
26 | @test sum([abs(stirlings1(8, i)) for i = 0:8]) == factorial(8)
27 |
28 | # bell
29 | @test bellnum(7) == 877
30 | @test bellnum(42) == parse(BigInt,"35742549198872617291353508656626642567")
31 | @test_throws DomainError bellnum(-1)
32 |
33 |
34 |
--------------------------------------------------------------------------------
/test/factorials.jl:
--------------------------------------------------------------------------------
1 | using Combinatorics
2 | using Base.Test
3 |
4 | @test factorial(7,3) == 7*6*5*4
5 | @test_throws DomainError factorial(3,7)
6 | @test_throws DomainError factorial(-3,-7)
7 | @test_throws DomainError factorial(-7,-3)
8 | #JuliaLang/julia#9943
9 | @test factorial(big(100), (80)) == 1303995018204712451095685346159820800000
10 | #JuliaLang/julia#9950
11 | @test_throws OverflowError factorial(1000,80)
12 |
13 | # derangement
14 | @test derangement(4) == subfactorial(4) == 9
15 | @test derangement(24) == parse(BigInt,"228250211305338670494289")
16 |
17 | # doublefactorial
18 | @test doublefactorial(70) == parse(BigInt,"355044260642859198243475901411974413130137600000000")
19 | @test_throws DomainError doublefactorial(-1)
20 |
21 | # hyperfactorial
22 | @test hyperfactorial(8) == parse(BigInt,"55696437941726556979200000")
23 |
24 | # multifactorial
25 | @test multifactorial(40,2) == doublefactorial(40)
26 | @test_throws DomainError multifactorial(-1,1)
27 |
28 | # multinomial
29 | @test multinomial(1,4,4,2) == 34650
30 |
31 | # primorial
32 | @test primorial(17) == 510510
33 | @test_throws DomainError primorial(-1)
34 |
35 |
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 | Combinatorics.jl is licensed under the MIT License:
2 |
3 | > Copyright (c) 2013-2015: Alessandro Andrioni, Jiahao Chen and other
4 | > contributors.
5 | >
6 | > Permission is hereby granted, free of charge, to any person obtaining
7 | > a copy of this software and associated documentation files (the
8 | > "Software"), to deal in the Software without restriction, including
9 | > without limitation the rights to use, copy, modify, merge, publish,
10 | > distribute, sublicense, and/or sell copies of the Software, and to
11 | > permit persons to whom the Software is furnished to do so, subject to
12 | > the following conditions:
13 | >
14 | > The above copyright notice and this permission notice shall be
15 | > included in all copies or substantial portions of the Software.
16 | >
17 | > THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
18 | > EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
19 | > MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
20 | > NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
21 | > LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
22 | > OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
23 | > WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
24 |
25 |
--------------------------------------------------------------------------------
/test/partitions.jl:
--------------------------------------------------------------------------------
1 | using Combinatorics
2 | using Base.Test
3 |
4 | @test collect(partitions(4)) == Any[[4], [3,1], [2,2], [2,1,1], [1,1,1,1]]
5 | @test collect(partitions(8,3)) == Any[[6,1,1], [5,2,1], [4,3,1], [4,2,2], [3,3,2]]
6 | @test collect(partitions(8, 1)) == Any[[8]]
7 | @test collect(partitions(8, 9)) == []
8 | @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]]]
9 | @test collect(partitions([1,2,3,4],3)) == Any[Any[[1,2],[3],[4]], Any[[1,3],[2],[4]], Any[[1],[2,3],[4]],
10 | Any[[1,4],[2],[3]], Any[[1],[2,4],[3]], Any[[1],[2],[3,4]]]
11 | @test collect(partitions([1,2,3,4],1)) == Any[Any[[1, 2, 3, 4]]]
12 | @test collect(partitions([1,2,3,4],5)) == []
13 |
14 | @test length(partitions(0)) == 1
15 | @test length(partitions(-1)) == 0
16 | @test length(collect(partitions(30))) == length(partitions(30))
17 | @test length(collect(partitions(90,4))) == length(partitions(90,4))
18 | @test length(collect(partitions('a':'h'))) == length(partitions('a':'h'))
19 | @test length(collect(partitions('a':'h',5))) == length(partitions('a':'h',5))
20 |
21 | # integer_partitions
22 | @test integer_partitions(0) == []
23 | @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]]
24 | @test_throws DomainError integer_partitions(-1)
25 |
26 | @test_throws ArgumentError prevprod([2,3,5],Int128(typemax(Int))+1)
27 | @test prevprod([2,3,5],30) == 30
28 | @test prevprod([2,3,5],33) == 32
29 |
30 | # noncrossing partitions
31 | let nc4 = ncpartitions(4)
32 | @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]],
33 | Any[[1,2],[3],[4]], Any[[1,2],[3,4]],
34 | Any[[1,3],[2],[4]], Any[[1,4],[2],[3]], Any[[1,4],[2,3]],
35 | Any[[1,2,3],[4]], Any[[1,3,4],[2]], Any[[1,2,4],[3]],
36 | Any[[1,2,3,4]]]
37 | @test length(nc4) == catalannum(4)
38 | end
39 |
--------------------------------------------------------------------------------
/src/factorials.jl:
--------------------------------------------------------------------------------
1 | #Factorials and elementary coefficients
2 |
3 | export
4 | derangement,
5 | factorial,
6 | subfactorial,
7 | doublefactorial,
8 | hyperfactorial,
9 | multifactorial,
10 | gamma,
11 | primorial,
12 | multinomial
13 |
14 | import Base: factorial
15 |
16 | "computes n!/k!"
17 | function factorial{T<:Integer}(n::T, k::T)
18 | if k < 0 || n < 0 || k > n
19 | throw(DomainError())
20 | end
21 | f = one(T)
22 | while n > k
23 | f = Base.checked_mul(f,n)
24 | n -= 1
25 | end
26 | return f
27 | end
28 | factorial(n::Integer, k::Integer) = factorial(promote(n, k)...)
29 |
30 |
31 | "The number of permutations of n with no fixed points (subfactorial)"
32 | function derangement(sn::Integer)
33 | n = BigInt(sn)
34 | return num(factorial(n)*sum([(-1)^k//factorial(k) for k=0:n]))
35 | end
36 | subfactorial(n::Integer) = derangement(n)
37 |
38 | function doublefactorial(n::Integer)
39 | if n < 0
40 | throw(DomainError())
41 | end
42 | z = BigInt()
43 | ccall((:__gmpz_2fac_ui, :libgmp), Void,
44 | (Ptr{BigInt}, UInt), &z, UInt(n))
45 | return z
46 | end
47 |
48 | # Hyperfactorial
49 | hyperfactorial(n::Integer) = prod([i^i for i = BigInt(2):n])
50 |
51 | function multifactorial(n::Integer, m::Integer)
52 | if n < 0
53 | throw(DomainError())
54 | end
55 | z = BigInt()
56 | ccall((:__gmpz_mfac_uiui, :libgmp), Void,
57 | (Ptr{BigInt}, UInt, UInt), &z, UInt(n), UInt(m))
58 | return z
59 | end
60 |
61 | function primorial(n::Integer)
62 | if n < 0
63 | throw(DomainError())
64 | end
65 | z = BigInt()
66 | ccall((:__gmpz_primorial_ui, :libgmp), Void,
67 | (Ptr{BigInt}, UInt), &z, UInt(n))
68 | return z
69 | end
70 |
71 | "Multinomial coefficient where n = sum(k)"
72 | function multinomial(k...)
73 | s = 0
74 | result = 1
75 | @inbounds for i in k
76 | s += i
77 | result *= binomial(s, i)
78 | end
79 | result
80 | end
81 |
82 |
83 |
--------------------------------------------------------------------------------
/test/combinations.jl:
--------------------------------------------------------------------------------
1 | using Combinatorics
2 | using Base.Test
3 |
4 | @test collect(combinations([])) == []
5 | @test collect(combinations(['a', 'b', 'c'])) == Any[['a'],['b'],['c'],['a','b'],['a','c'],['b','c'],['a','b','c']]
6 |
7 | @test collect(combinations("abc",3)) == Any[['a','b','c']]
8 | @test collect(combinations("abc",2)) == Any[['a','b'],['a','c'],['b','c']]
9 | @test collect(combinations("abc",1)) == Any[['a'],['b'],['c']]
10 | @test collect(combinations("abc",0)) == Any[[]]
11 | @test collect(combinations("abc",-1)) == []
12 |
13 | @test collect(filter(x->(iseven(x[1])),combinations([1,2,3],2))) == Any[[2,3]]
14 |
15 | # multiset_combinations
16 | @test collect(multiset_combinations("aabc", 5)) == Any[]
17 | @test collect(multiset_combinations("aabc", 2)) == Any[['a','a'],['a','b'],['a','c'],['b','c']]
18 | @test collect(multiset_combinations("aabc", 1)) == Any[['a'],['b'],['c']]
19 | @test collect(multiset_combinations("aabc", 0)) == Any[Char[]]
20 | @test collect(multiset_combinations("aabc", -1)) == Any[]
21 | @test collect(multiset_combinations("", 1)) == Any[]
22 | @test collect(multiset_combinations("", 0)) == Any[Char[]]
23 | @test collect(multiset_combinations("", -1)) == Any[]
24 |
25 | # with_replacement_combinations
26 | @test collect(with_replacement_combinations("abc", 2)) == Any[['a','a'],['a','b'],['a','c'],
27 | ['b','b'],['b','c'],['c','c']]
28 | @test collect(with_replacement_combinations("abc", 1)) == Any[['a'],['b'],['c']]
29 | @test collect(with_replacement_combinations("abc", 0)) == Any[Char[]]
30 | @test collect(with_replacement_combinations("abc", -1)) == Any[]
31 | @test collect(with_replacement_combinations("", 1)) == Any[]
32 | @test collect(with_replacement_combinations("", 0)) == Any[Char[]]
33 | @test collect(with_replacement_combinations("", -1)) == Any[]
34 |
35 |
36 | #cool-lex iterator
37 | @test_throws DomainError collect(CoolLexCombinations(-1, 1))
38 | @test_throws DomainError collect(CoolLexCombinations(5, 0))
39 | @test collect(CoolLexCombinations(4,2)) == Vector[[1,2], [2,3], [1,3], [2,4], [3,4], [1,4]]
40 | @test isa(start(CoolLexCombinations(1000, 20)), Combinatorics.CoolLexIterState{BigInt})
41 |
--------------------------------------------------------------------------------
/src/numbers.jl:
--------------------------------------------------------------------------------
1 | #Special named numbers and symbols
2 |
3 | export bellnum,
4 | catalannum,
5 | fibonaccinum,
6 | jacobisymbol,
7 | lassallenum,
8 | legendresymbol,
9 | lucasnum,
10 | stirlings1
11 |
12 | "Returns the n-th Bell number"
13 | function bellnum(bn::Integer)
14 | if bn < 0
15 | throw(DomainError())
16 | else
17 | n = BigInt(bn)
18 | end
19 | list = Array(BigInt, div(n*(n+1), 2))
20 | list[1] = 1
21 | for i = 2:n
22 | beg = div(i*(i-1),2)
23 | list[beg+1] = list[beg]
24 | for j = 2:i
25 | list[beg+j] = list[beg+j-1]+list[beg+j-i]
26 | end
27 | end
28 | return list[end]
29 | end
30 |
31 | "Returns the n-th Catalan number"
32 | function catalannum(bn::Integer)
33 | if bn<0
34 | throw(DomainError())
35 | else
36 | n = BigInt(bn)
37 | end
38 | div(binomial(2*n, n), (n + 1))
39 | end
40 |
41 | function fibonaccinum(n::Integer)
42 | if n < 0
43 | throw(DomainError())
44 | end
45 | z = BigInt()
46 | ccall((:__gmpz_fib_ui, :libgmp), Void,
47 | (Ptr{BigInt}, UInt), &z, UInt(n))
48 | return z
49 | end
50 |
51 |
52 | function jacobisymbol(a::Integer, b::Integer)
53 | ba = BigInt(a)
54 | bb = BigInt(b)
55 | return ccall((:__gmpz_jacobi, :libgmp), Cint,
56 | (Ptr{BigInt}, Ptr{BigInt}), &ba, &bb)
57 | end
58 |
59 | """
60 | Computes Lassalle's sequence
61 | OEIS entry A180874
62 | """
63 | function lassallenum(m::Integer)
64 | A = ones(BigInt,m)
65 | for n=2:m
66 | A[n]=(-1)^(n-1) * (catalannum(n) + sum([(-1)^j*binomial(2n-1, 2j-1)*A[j]*catalannum(n-j) for j=1:n-1]))
67 | end
68 | A[m]
69 | end
70 |
71 | function legendresymbol(a::Integer, b::Integer)
72 | ba = BigInt(a)
73 | bb = BigInt(b)
74 | return ccall((:__gmpz_legendre, :libgmp), Cint,
75 | (Ptr{BigInt}, Ptr{BigInt}), &ba, &bb)
76 | end
77 |
78 | function lucasnum(n::Integer)
79 | if n < 0
80 | throw(DomainError())
81 | end
82 | z = BigInt()
83 | ccall((:__gmpz_lucnum_ui, :libgmp), Void,
84 | (Ptr{BigInt}, UInt), &z, UInt(n))
85 | return z
86 | end
87 |
88 | "Returns s(n, k), the signed Stirling number of first kind"
89 | function stirlings1(n::Integer, k::Integer)
90 | p = poly(0:(n-1))
91 | p[n - k + 1]
92 | end
93 |
94 |
--------------------------------------------------------------------------------
/test/permutations.jl:
--------------------------------------------------------------------------------
1 | using Combinatorics
2 | using Base.Test
3 |
4 | # permutations
5 | @test collect(permutations("abc")) == Any[['a','b','c'],['a','c','b'],['b','a','c'],
6 | ['b','c','a'],['c','a','b'],['c','b','a']]
7 |
8 | @test collect(filter(x->(iseven(x[1])),permutations([1,2,3]))) == Any[[2,1,3],[2,3,1]]
9 | @test collect(filter(x->(iseven(x[3])),permutations([1,2,3]))) == Any[[1,3,2],[3,1,2]]
10 |
11 | @test length(permutations(0)) == 1
12 |
13 | @test collect(permutations("abc", 4)) == Any[]
14 | @test collect(permutations("abc", 2)) == Any[['a','b'],['a','c'],['b','a'],
15 | ['b','c'],['c','a'],['c','b']]
16 | @test collect(permutations("abc", 0)) == Any[Char[]]
17 | @test collect(permutations("abc", -1)) == Any[]
18 | @test collect(permutations("", 1)) == Any[]
19 | @test collect(permutations("", 0)) == Any[Char[]]
20 | @test collect(permutations("", -1)) == Any[]
21 |
22 | # multiset_permutations
23 | @test collect(multiset_permutations("aabc", 5)) == Any[]
24 | @test collect(multiset_permutations("aabc", 2)) == Any[['a','a'],['a','b'], ['a','c'],['b','a'],
25 | ['b','c'],['c','a'],['c','b']]
26 | @test collect(multiset_permutations("aabc", 0)) == Any[Char[]]
27 | @test collect(multiset_permutations("aabc", -1)) == Any[]
28 | @test collect(multiset_permutations("", 1)) == Any[]
29 | @test collect(multiset_permutations("", 0)) == Any[Char[]]
30 | @test collect(multiset_permutations("", -1)) == Any[]
31 | @test length(multiset_permutations("aaaaaaaaaaaaaaaaaaaaab", 21)) == 22
32 |
33 | #nthperm!
34 | for n = 0:7, k = 1:factorial(n)
35 | p = nthperm!([1:n;], k)
36 | @test isperm(p)
37 | @test nthperm(p) == k
38 | end
39 | #Euler Problem #24
40 | @test nthperm!([0:9;],1000000) == [2,7,8,3,9,1,5,4,6,0]
41 |
42 | @test nthperm([0,1,2],3) == [1,0,2]
43 |
44 | @test_throws ArgumentError parity([0])
45 | @test_throws ArgumentError parity([1,2,3,3])
46 | @test levicivita([1,1,2,3]) == 0
47 | @test levicivita([1]) == 1 && parity([1]) == 0
48 | @test map(levicivita, collect(permutations([1,2,3]))) == [1, -1, -1, 1, 1, -1]
49 | @test let p = [3, 4, 6, 10, 5, 2, 1, 7, 8, 9]; levicivita(p) == 1 && parity(p) == 0; end
50 | @test let p = [4, 3, 6, 10, 5, 2, 1, 7, 8, 9]; levicivita(p) == -1 && parity(p) == 1; end
51 |
52 | @test Combinatorics.nsetpartitions(-1) == 0
53 | @test collect(permutations([])) == Vector[[]]
54 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Combinatorics
2 |
3 | [](http://pkg.julialang.org/?pkg=Combinatorics&ver=0.4)
4 | [](https://travis-ci.org/JuliaLang/Combinatorics.jl)
5 | [](https://coveralls.io/github/JuliaLang/Combinatorics.jl?branch=master)
6 | [](https://codecov.io/github/JuliaLang/Combinatorics.jl?branch=master)
7 |
8 | A combinatorics library for Julia, focusing mostly (as of now) on enumerative
9 | combinatorics and permutations. As overflows are expected even for low values,
10 | most of the functions always return `BigInt`, and are marked as such below.
11 |
12 | This library provides the following functions:
13 | - `bellnum(n)`: returns the n-th Bell number; always returns a `BigInt`;
14 | - `catalannum(n)`: returns the n-th Catalan number; always returns a `BigInt`;
15 | - `combinations(a)`: returns combinations of all order by chaining calls to `Base.combinations(a,n);
16 | - `derangement(n)`/`subfactorial(n)`: returns the number of permutations of n with no fixed points; always returns a `BigInt`;
17 | - `doublefactorial(n)`: returns the double factorial n!!; always returns a `BigInt`;
18 | - `fibonacci(n)`: the n-th Fibonacci number; always returns a `BigInt`;
19 | - `hyperfactorial(n)`: the n-th hyperfactorial, i.e. prod([i^i for i = 2:n]; always returns a `BigInt`;
20 | - `integer_partitions(n)`: returns a `Vector{Int}` consisting of the partitions of the number `n`.
21 | - `jacobisymbol(a,b)`: returns the Jacobi symbol (a/b);
22 | - `lassallenum(n)`: returns the nth Lassalle number An defined in [arXiv:1009.4225](http://arxiv.org/abs/1009.4225) ([OEIS A180874](http://oeis.org/A180874)); always returns a `BigInt`;
23 | - `legendresymbol(a,p)`: returns the Legendre symbol (a/p);
24 | - `lucasnum(n)`: the n-th Lucas number; always returns a `BigInt`;
25 | - `multifactorial(n)`: returns the m-multifactorial n(!^m); always returns a `BigInt`;
26 | - `multinomial(k...)`: receives a tuple of `k_1, ..., k_n` and calculates the multinomial coefficient `(n k)`, where `n = sum(k)`; returns a `BigInt` only if given a `BigInt`;
27 | - `primorial(n)`: returns the product of all positive prime numbers <= n; always returns a `BigInt`;
28 | - `stirlings1(n, k)`: the signed `(n,k)`-th Stirling number of the first kind; returns a `BigInt` only if given a `BigInt`.
29 |
30 | Young diagrams
31 | --------------
32 | Limited support for working with Young diagrams is provided.
33 |
34 | - `partitionsequence(a)`: computes partition sequence for an integer partition `a`
35 | - `x = a \ b` creates the skew diagram for partitions (tuples) `a`, `b`
36 | - `isrimhook(x)`: checks if skew diagram `x` is a rim hook
37 | - `leglength(x)`: computes leg length of rim hook `x`
38 | - `character(a, b)`: computes character the partition `b` in the `a`th irrep of Sn
39 |
--------------------------------------------------------------------------------
/src/youngdiagrams.jl:
--------------------------------------------------------------------------------
1 | ################################################################################
2 | # Young diagrams, partitions of unity and characters of the symmetric group Sn #
3 | ################################################################################
4 |
5 | typealias Partition Vector{Int}
6 | typealias YoungDiagram Array{Int,2}
7 | typealias SkewDiagram Tuple{Partition, Partition}
8 |
9 | export Partition,
10 | YoungDiagram, #represents shape of Young diagram
11 | SkewDiagram, #skew diagrams
12 | partitionsequence,
13 | isrimhook, #Check if skew diagram is rim hook
14 | leglength,
15 | character #Computes character of irrep of Sn
16 |
17 | import Base.\
18 |
19 | #################
20 | # Skew diagrams #
21 | #################
22 |
23 | "This uses a very simple internal representation for skew diagrams"
24 | \(λ::Partition, μ::Partition) = MakeSkewDiagram(λ, μ)
25 | function MakeSkewDiagram(λ::Partition, μ::Partition)
26 | m, n = length(λ), length(μ)
27 | if n>m error("Cannot construct skew diagram") end
28 | (λ, μ)
29 | end
30 |
31 | "Checks if skew diagram is a rim hook"
32 | isrimhook(λ::Partition, μ::Partition)=isrimhook(λ \ μ)
33 | function isrimhook(ξ::SkewDiagram)
34 | λ, μ = ξ
35 | m, n = length(λ), length(μ)
36 | if n>m error("Cannot construct skew diagram") end
37 | #Construct matrix representation of diagram
38 | #XXX This is a horribly inefficient way of checking condition 1!
39 | l = maximum(λ)
40 | youngdiagram=zeros(Int, m, l)
41 | for i=1:n
42 | youngdiagram[i, μ[i]+1:λ[i]]=1
43 | end
44 | for i=n+1:m
45 | youngdiagram[i, 1:λ[i]]=1
46 | end
47 | #Condition 1. Must be edgewise connected
48 | youngdiagramList=[]
49 | for i=1:m
50 | for j=1:l
51 | if youngdiagram[i, j]==1 youngdiagramList = [youngdiagramList; (i, j)] end
52 | end
53 | end
54 | for k=1:length(youngdiagramList)
55 | i, j = youngdiagramList[k]
56 | numNeighbors = 0
57 | for kp=1:length(youngdiagramList)
58 | ip,jp= youngdiagramList[kp]
59 | if abs(i-ip) + abs(j-jp) == 1 numNeighbors += 1 end
60 | end
61 | if numNeighbors == 0 return false end #Found a cell with no adjacent neighbors
62 | end
63 | #Condition 2. Must not contain 2x2 square of cells
64 | for i=1:m-1
65 | for j=1:l-1
66 | if youngdiagram[i, j]== 0 continue end
67 | if youngdiagram[i, j+1] == youngdiagram[i+1, j] == youngdiagram[i+1, j+1] == 1
68 | return false
69 | end
70 | end
71 | end
72 | return true
73 | end
74 |
75 |
76 | "Strictly speaking, defined for rim hook only, but here we define it for all skew diagrams"
77 | leglength(λ::Partition, μ::Partition)=leglength((λ \ μ))
78 | function leglength(ξ::SkewDiagram)
79 | λ, μ = ξ
80 | m, n = length(λ), length(μ)
81 | #Construct matrix representation of diagram
82 | if m==0 return -1 end
83 | l = maximum(λ)
84 | youngdiagram=zeros(Int, m, l)
85 | for i=1:n
86 | youngdiagram[i, μ[i]+1:λ[i]]=1
87 | end
88 | for i=n+1:m
89 | youngdiagram[i, 1:λ[i]]=1
90 | end
91 | for i=m:-1:1
92 | if any([x==1 for x in youngdiagram[i,:]]) return i-1 end
93 | end
94 | return -1 #If entire matrix is empty
95 | end
96 |
97 |
98 | #######################
99 | # partition sequences #
100 | #######################
101 |
102 | "Computes essential part of the partition sequence of lambda"
103 | function partitionsequence(lambda::Partition)
104 | Λ▔ = Int[]
105 | λ = [lambda; 0]
106 | m = length(lambda)
107 | for i=m:-1:1
108 | for k=1:(λ[i]-λ[i+1])
109 | Λ▔ = [Λ▔; 1]
110 | end
111 | Λ▔ = [Λ▔ ; 0]
112 | end
113 | Λ▔
114 | end
115 |
116 | "Takes two elements of a partition sequence, with a to the left of b"
117 | isrimhook(a::Int, b::Int) = (a==1) && (b==0)
118 |
119 |
120 | #############################
121 | # Character of irreps of Sn #
122 | #############################
123 |
124 | "Computes recursively using the Murnaghan-Nakayama rule."
125 | function MN1inner(R::Vector{Int}, T::Dict, μ::Partition, t::Integer)
126 | s=length(R)
127 | χ::Integer=1
128 | if t<=length(μ)
129 | χ, σ::Integer = 0, 1
130 | for j=1:μ[t]-1
131 | if R[j]==0 σ=-σ end
132 | end
133 | for i=1:s-μ[t]
134 | if R[i] != R[i+μ[t]-1] σ=-σ end
135 | if isrimhook(R[i], R[i+μ[t]])
136 | R[i], R[i+μ[t]] = R[i+μ[t]], R[i]
137 | rhohat = R[i:i+μ[t]]
138 | if !haskey(T, rhohat) #Cache result in lookup table
139 | T[rhohat] = MN1inner(R, T, μ, t+1)
140 | end
141 | χ += σ * T[rhohat]
142 | R[i], R[i+μ[t]] = R[i+μ[t]], R[i]
143 | end
144 | end
145 | end
146 | χ
147 | end
148 |
149 | doc"""
150 | Computes character $χ^λ(μ)$ of the partition μ in the λth irrep of the
151 | symmetric group $S_n$
152 |
153 | Implements the Murnaghan-Nakayama algorithm as described in:
154 | Dan Bernstein,
155 | "The computational complexity of rules for the character table of Sn",
156 | Journal of Symbolic Computation, vol. 37 iss. 6 (2004), pp 727-748.
157 | doi:10.1016/j.jsc.2003.11.001
158 | """
159 | function character(λ::Partition, μ::Partition)
160 | T = Dict{Any,Int}(()=>0) #Sparse array implemented as dict
161 | Λ▔ = partitionsequence(λ)
162 | MN1inner(Λ▔, T, μ, 1)
163 | end
164 |
--------------------------------------------------------------------------------
/src/permutations.jl:
--------------------------------------------------------------------------------
1 | #Permutations
2 |
3 | export
4 | levicivita,
5 | multiset_permutations,
6 | nthperm!,
7 | nthperm,
8 | parity,
9 | permutations
10 |
11 |
12 | immutable Permutations{T}
13 | a::T
14 | t::Int
15 | end
16 |
17 | eltype{T}(::Type{Permutations{T}}) = Vector{eltype(T)}
18 |
19 | length(p::Permutations) = (0 <= p.t <= length(p.a))?factorial(length(p.a), length(p.a)-p.t):0
20 |
21 | """
22 | Generate all permutations of an indexable object. Because the number of permutations can be very large, this function returns an iterator object. Use `collect(permutations(array))` to get an array of all permutations.
23 | """
24 | permutations(a) = Permutations(a, length(a))
25 |
26 | """
27 | Generate all size t permutations of an indexable object.
28 | """
29 | function permutations(a, t::Integer)
30 | if t < 0
31 | t = length(a) + 1
32 | end
33 | Permutations(a, t)
34 | end
35 |
36 | start(p::Permutations) = [1:length(p.a);]
37 | next(p::Permutations, s) = nextpermutation(p.a, p.t ,s)
38 |
39 | function nextpermutation(m, t, s)
40 | perm = [m[s[i]] for i in 1:t]
41 | n = length(s)
42 | if t <= 0
43 | return(perm, [n+1])
44 | end
45 | s = copy(s)
46 | if t < n
47 | j = t + 1
48 | while j <= n && s[t] >= s[j]; j+=1; end
49 | end
50 | if t < n && j <= n
51 | s[t], s[j] = s[j], s[t]
52 | else
53 | if t < n
54 | reverse!(s, t+1)
55 | end
56 | i = t - 1
57 | while i>=1 && s[i] >= s[i+1]; i -= 1; end
58 | if i > 0
59 | j = n
60 | while j>i && s[i] >= s[j]; j -= 1; end
61 | s[i], s[j] = s[j], s[i]
62 | reverse!(s, i+1)
63 | else
64 | s[1] = n+1
65 | end
66 | end
67 | return(perm, s)
68 | end
69 |
70 | done(p::Permutations, s) = !isempty(s) && max(s[1], p.t) > length(p.a) || (isempty(s) && p.t > 0)
71 |
72 | immutable MultiSetPermutations{T}
73 | m::T
74 | f::Vector{Int}
75 | t::Int
76 | ref::Vector{Int}
77 | end
78 |
79 | eltype{T}(::Type{MultiSetPermutations{T}}) = Vector{eltype(T)}
80 |
81 | function length(c::MultiSetPermutations)
82 | t = c.t
83 | if t > length(c.ref)
84 | return 0
85 | end
86 | if t > 20
87 | g = [factorial(big(i)) for i in 0:t]
88 | else
89 | g = [factorial(i) for i in 0:t]
90 | end
91 | p = [g[t+1]; zeros(Float64,t)]
92 | for i in 1:length(c.f)
93 | f = c.f[i]
94 | if i == 1
95 | for j in 1:min(f, t)
96 | p[j+1] = g[t+1]/g[j+1]
97 | end
98 | else
99 | for j in t:-1:1
100 | q = 0
101 | for k in (j+1):-1:max(1,j+1-f)
102 | q += p[k]/g[j+2-k]
103 | end
104 | p[j+1] = q
105 | end
106 | end
107 | end
108 | return round(Int, p[t+1])
109 | end
110 |
111 | "generate all permutations of size t from an array a with possibly duplicated elements."
112 | function multiset_permutations{T<:Integer}(m, f::Vector{T}, t::Integer)
113 | length(m) == length(f) || error("Lengths of m and f are not the same.")
114 | ref = length(f) > 0 ? vcat([[i for j in 1:f[i] ] for i in 1:length(f)]...) : Int[]
115 | if t < 0
116 | t = length(ref) + 1
117 | end
118 | MultiSetPermutations(m, f, t, ref)
119 | end
120 |
121 | function multiset_permutations{T}(a::T, t::Integer)
122 | m = unique(collect(a))
123 | f = [sum([c == x for c in a]) for x in m]
124 | multiset_permutations(m, f, t)
125 | end
126 |
127 | start(p::MultiSetPermutations) = p.ref
128 | next(p::MultiSetPermutations, s) = nextpermutation(p.m, p.t, s)
129 | done(p::MultiSetPermutations, s) =
130 | !isempty(s) && max(s[1], p.t) > length(p.ref) || (isempty(s) && p.t > 0)
131 |
132 |
133 |
134 | "In-place version of nthperm."
135 | function nthperm!(a::AbstractVector, k::Integer)
136 | k -= 1 # make k 1-indexed
137 | k < 0 && throw(ArgumentError("permutation k must be ≥ 0, got $k"))
138 | n = length(a)
139 | n == 0 && return a
140 | f = factorial(oftype(k, n-1))
141 | for i=1:n-1
142 | j = div(k, f) + 1
143 | k = k % f
144 | f = div(f, n-i)
145 |
146 | j = j+i-1
147 | elt = a[j]
148 | for d = j:-1:i+1
149 | a[d] = a[d-1]
150 | end
151 | a[i] = elt
152 | end
153 | a
154 | end
155 |
156 | "Compute the kth lexicographic permutation of the vector a."
157 | nthperm(a::AbstractVector, k::Integer) = nthperm!(copy(a),k)
158 |
159 | "Return the `k` that generated permutation `p`. Note that `nthperm(nthperm([1:n], k)) == k` for `1 <= k <= factorial(n)`."
160 | function nthperm{T<:Integer}(p::AbstractVector{T})
161 | isperm(p) || throw(ArgumentError("argument is not a permutation"))
162 | k, n = 1, length(p)
163 | for i = 1:n-1
164 | f = factorial(n-i)
165 | for j = i+1:n
166 | k += ifelse(p[j] < p[i], f, 0)
167 | end
168 | end
169 | return k
170 | end
171 |
172 |
173 | # Parity of permutations
174 |
175 | const levicivita_lut = cat(3, [0 0 0; 0 0 1; 0 -1 0],
176 | [0 0 -1; 0 0 0; 1 0 0],
177 | [0 1 0; -1 0 0; 0 0 0])
178 |
179 | """
180 | Levi-Civita symbol of a permutation.
181 |
182 | Returns 1 is the permutation is even, -1 if it is odd and 0 otherwise.
183 |
184 | The parity is computed by using the fact that a permutation is odd if and
185 | only if the number of even-length cycles is odd.
186 | """
187 | function levicivita{T<:Integer}(p::AbstractVector{T})
188 | n = length(p)
189 |
190 | if n == 3
191 | @inbounds valid = (0 < p[1] <= 3) * (0 < p[2] <= 3) * (0 < p[3] <= 3)
192 | return valid ? levicivita_lut[p[1], p[2], p[3]] : 0
193 | end
194 |
195 | todo = trues(n)
196 | first = 1
197 | cycles = flips = 0
198 |
199 | while cycles + flips < n
200 | first = findnext(todo, first)
201 | (todo[first] $= true) && return 0
202 | j = p[first]
203 | (0 < j <= n) || return 0
204 | cycles += 1
205 | while j ≠ first
206 | (todo[j] $= true) && return 0
207 | j = p[j]
208 | (0 < j <= n) || return 0
209 | flips += 1
210 | end
211 | end
212 |
213 | return iseven(flips) ? 1 : -1
214 | end
215 |
216 | """
217 | Computes the parity of a permutation using the levicivita function,
218 | so you can ask iseven(parity(p)). If p is not a permutation throws an error.
219 | """
220 | function parity{T<:Integer}(p::AbstractVector{T})
221 | epsilon = levicivita(p)
222 | epsilon == 0 && throw(ArgumentError("Not a permutation"))
223 | epsilon == 1 ? 0 : 1
224 | end
225 |
--------------------------------------------------------------------------------
/src/combinations.jl:
--------------------------------------------------------------------------------
1 | export combinations,
2 | CoolLexCombinations,
3 | multiset_combinations,
4 | with_replacement_combinations
5 |
6 | #The Combinations iterator
7 |
8 | immutable Combinations{T}
9 | a::T
10 | t::Int
11 | end
12 |
13 | start(c::Combinations) = [1:c.t;]
14 | function next(c::Combinations, s)
15 | comb = [c.a[si] for si in s]
16 | if c.t == 0
17 | # special case to generate 1 result for t==0
18 | return (comb,[length(c.a)+2])
19 | end
20 | s = copy(s)
21 | for i = length(s):-1:1
22 | s[i] += 1
23 | if s[i] > (length(c.a) - (length(s)-i))
24 | continue
25 | end
26 | for j = i+1:endof(s)
27 | s[j] = s[j-1]+1
28 | end
29 | break
30 | end
31 | (comb,s)
32 | end
33 | done(c::Combinations, s) = !isempty(s) && s[1] > length(c.a)-c.t+1
34 |
35 | length(c::Combinations) = binomial(length(c.a),c.t)
36 |
37 | eltype{T}(::Type{Combinations{T}}) = Vector{eltype(T)}
38 |
39 | "Generate all combinations of `n` elements from an indexable object. Because the number of combinations can be very large, this function returns an iterator object. Use `collect(combinations(array,n))` to get an array of all combinations.
40 | "
41 | function combinations(a, t::Integer)
42 | if t < 0
43 | # generate 0 combinations for negative argument
44 | t = length(a)+1
45 | end
46 | Combinations(a, t)
47 | end
48 |
49 |
50 | """
51 | generate combinations of all orders, chaining of order iterators is eager,
52 | but sequence at each order is lazy
53 | """
54 | combinations(a) = chain([combinations(a,k) for k=1:length(a)]...)
55 |
56 |
57 |
58 | # cool-lex combinations iterator
59 |
60 | """
61 | Produces (n,k)-combinations in cool-lex order
62 |
63 | Implements the cool-lex algorithm to generate (n,k)-combinations
64 | @article{Ruskey:2009fk,
65 | Author = {Frank Ruskey and Aaron Williams},
66 | Doi = {10.1016/j.disc.2007.11.048},
67 | Journal = {Discrete Mathematics},
68 | Month = {September},
69 | Number = {17},
70 | Pages = {5305-5320},
71 | Title = {The coolest way to generate combinations},
72 | Url = {http://www.sciencedirect.com/science/article/pii/S0012365X07009570},
73 | Volume = {309},
74 | Year = {2009}}
75 | """
76 | immutable CoolLexCombinations
77 | n :: Int
78 | t :: Int
79 | end
80 |
81 | immutable CoolLexIterState{T<:Integer}
82 | R0:: T
83 | R1:: T
84 | R2:: T
85 | R3:: T
86 | end
87 |
88 | function start(C::CoolLexCombinations)
89 | if C.n < 0
90 | throw(DomainError())
91 | end
92 | if C.t ≤ 0
93 | throw(DomainError())
94 | end
95 |
96 | #What integer size should I use?
97 | if C.n < 8sizeof(Int)
98 | T = Int
99 | else
100 | T = BigInt
101 | end
102 |
103 | CoolLexIterState{T}(0, 0, T(1) << C.n, (T(1) << C.t) - 1)
104 | end
105 |
106 | function next(C::CoolLexCombinations, S::CoolLexIterState)
107 | R0 = S.R0
108 | R1 = S.R1
109 | R2 = S.R2
110 | R3 = S.R3
111 |
112 | R0 = R3 & (R3 + 1)
113 | R1 = R0 $ (R0 - 1)
114 | R0 = R1 + 1
115 | R1 &= R3
116 | R0 = max((R0 & R3) - 1, 0)
117 | R3 += R1 - R0
118 |
119 | _cool_lex_visit(S.R3), CoolLexIterState(R0, R1, R2, R3)
120 | end
121 |
122 | #Converts an integer bit pattern X into a subset
123 | #If X & 2^k == 1, then k is in the subset
124 | function _cool_lex_visit(X::Int)
125 | subset = Int[]
126 | n=1
127 | while X != 0
128 | if X & 1 == 1 push!(subset, n) end
129 | X >>= 1
130 | n += 1
131 | end
132 | subset
133 | end
134 |
135 | done(C::CoolLexCombinations, S::CoolLexIterState) = (S.R3 & S.R2 != 0)
136 |
137 | length(C::CoolLexCombinations) = max(0, binomial(C.n, C.t))
138 |
139 |
140 | immutable MultiSetCombinations{T}
141 | m::T
142 | f::Vector{Int}
143 | t::Int
144 | ref::Vector{Int}
145 | end
146 |
147 | eltype{T}(::Type{MultiSetCombinations{T}}) = Vector{eltype(T)}
148 |
149 | function length(c::MultiSetCombinations)
150 | t = c.t
151 | if t > length(c.ref)
152 | return 0
153 | end
154 | p = [1; zeros(Int,t)]
155 | for i in 1:length(c.f)
156 | f = c.f[i]
157 | if i == 1
158 | for j in 1:min(f, t)
159 | p[j+1] = 1
160 | end
161 | else
162 | for j in t:-1:1
163 | p[j+1] = sum(p[max(1,j+1-f):(j+1)])
164 | end
165 | end
166 | end
167 | return p[t+1]
168 | end
169 |
170 | function multiset_combinations{T<:Integer}(m, f::Vector{T}, t::Integer)
171 | length(m) == length(f) || error("Lengths of m and f are not the same.")
172 | ref = length(f) > 0 ? vcat([[i for j in 1:f[i] ] for i in 1:length(f)]...) : Int[]
173 | if t < 0
174 | t = length(ref) + 1
175 | end
176 | MultiSetCombinations(m, f, t, ref)
177 | end
178 |
179 | "generate all combinations of size t from an array a with possibly duplicated elements."
180 | function multiset_combinations{T}(a::T, t::Integer)
181 | m = unique(collect(a))
182 | f = Int[sum([c == x for c in a]) for x in m]
183 | multiset_combinations(m, f, t)
184 | end
185 |
186 | start(c::MultiSetCombinations) = c.ref
187 | function next(c::MultiSetCombinations, s)
188 | ref = c.ref
189 | n = length(ref)
190 | t = c.t
191 | changed = false
192 | comb = [c.m[s[i]] for i in 1:t]
193 | if t > 0
194 | s = copy(s)
195 | for i in t:-1:1
196 | if s[i] < ref[i + (n - t)]
197 | j = 1
198 | while ref[j] <= s[i]; j += 1; end
199 | s[i] = ref[j]
200 | for l in (i+1):t
201 | s[l] = ref[j+=1]
202 | end
203 | changed = true
204 | break
205 | end
206 | end
207 | !changed && (s[1] = n+1)
208 | else
209 | s = [n+1]
210 | end
211 | (comb, s)
212 | end
213 | done(c::MultiSetCombinations, s) =
214 | (!isempty(s) && max(s[1], c.t) > length(c.ref)) || (isempty(s) && c.t > 0)
215 |
216 | immutable WithReplacementCombinations{T}
217 | a::T
218 | t::Int
219 | end
220 |
221 | eltype{T}(::Type{WithReplacementCombinations{T}}) = Vector{eltype(T)}
222 |
223 | length(c::WithReplacementCombinations) = binomial(length(c.a)+c.t-1, c.t)
224 |
225 | "generate all combinations with replacement of size t from an array a."
226 | with_replacement_combinations(a, t::Integer) = WithReplacementCombinations(a, t)
227 |
228 | start(c::WithReplacementCombinations) = [1 for i in 1:c.t]
229 | function next(c::WithReplacementCombinations, s)
230 | n = length(c.a)
231 | t = c.t
232 | comb = [c.a[si] for si in s]
233 | if t > 0
234 | s = copy(s)
235 | changed = false
236 | for i in t:-1:1
237 | if s[i] < n
238 | s[i] += 1
239 | for j in (i+1):t
240 | s[j] = s[i]
241 | end
242 | changed = true
243 | break
244 | end
245 | end
246 | !changed && (s[1] = n+1)
247 | else
248 | s = [n+1]
249 | end
250 | (comb, s)
251 | end
252 | done(c::WithReplacementCombinations, s) = !isempty(s) && s[1] > length(c.a) || c.t < 0
253 |
--------------------------------------------------------------------------------
/src/partitions.jl:
--------------------------------------------------------------------------------
1 | #Partitions
2 |
3 | export
4 | integer_partitions,
5 | ncpartitions,
6 | partitions,
7 | prevprod
8 | #nextprod,
9 |
10 |
11 | #integer partitions
12 |
13 | immutable IntegerPartitions
14 | n::Int
15 | end
16 |
17 | length(p::IntegerPartitions) = npartitions(p.n)
18 |
19 | start(p::IntegerPartitions) = Int[]
20 | done(p::IntegerPartitions, xs) = length(xs) == p.n
21 | next(p::IntegerPartitions, xs) = (xs = nextpartition(p.n,xs); (xs,xs))
22 |
23 | """
24 | 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))`.
25 | """
26 | partitions(n::Integer) = IntegerPartitions(n)
27 |
28 |
29 |
30 | function nextpartition(n, as)
31 | if isempty(as); return Int[n]; end
32 |
33 | xs = similar(as,0)
34 | sizehint!(xs,length(as)+1)
35 |
36 | for i = 1:length(as)-1
37 | if as[i+1] == 1
38 | x = as[i]-1
39 | push!(xs, x)
40 | n -= x
41 | while n > x
42 | push!(xs, x)
43 | n -= x
44 | end
45 | push!(xs, n)
46 |
47 | return xs
48 | end
49 | push!(xs, as[i])
50 | n -= as[i]
51 | end
52 | push!(xs, as[end]-1)
53 | push!(xs, 1)
54 |
55 | xs
56 | end
57 |
58 | let _npartitions = Dict{Int,Int}()
59 | global npartitions
60 | function npartitions(n::Int)
61 | if n < 0
62 | 0
63 | elseif n < 2
64 | 1
65 | elseif (np = get(_npartitions, n, 0)) > 0
66 | np
67 | else
68 | np = 0
69 | sgn = 1
70 | for k = 1:n
71 | np += sgn * (npartitions(n-k*(3k-1)>>1) + npartitions(n-k*(3k+1)>>1))
72 | sgn = -sgn
73 | end
74 | _npartitions[n] = np
75 | end
76 | end
77 | end
78 |
79 | # Algorithm H from TAoCP 7.2.1.4
80 | # Partition n into m parts
81 | # in colex order (lexicographic by reflected sequence)
82 |
83 | immutable FixedPartitions
84 | n::Int
85 | m::Int
86 | end
87 |
88 | length(f::FixedPartitions) = npartitions(f.n,f.m)
89 |
90 | """
91 | 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))`.
92 | """
93 | partitions(n::Integer, m::Integer) = n >= 1 && m >= 1 ? FixedPartitions(n,m) : throw(DomainError())
94 |
95 | start(f::FixedPartitions) = Int[]
96 | function done(f::FixedPartitions, s::Vector{Int})
97 | f.m <= f.n || return true
98 | isempty(s) && return false
99 | return f.m == 1 || s[1]-1 <= s[end]
100 | end
101 | next(f::FixedPartitions, s::Vector{Int}) = (xs = nextfixedpartition(f.n,f.m,s); (xs,xs))
102 |
103 | function nextfixedpartition(n, m, bs)
104 | as = copy(bs)
105 | if isempty(as)
106 | # First iteration
107 | as = [n-m+1; ones(Int, m-1)]
108 | elseif as[2] < as[1]-1
109 | # Most common iteration
110 | as[1] -= 1
111 | as[2] += 1
112 | else
113 | # Iterate
114 | local j
115 | s = as[1]+as[2]-1
116 | for j = 3:m
117 | if as[j] < as[1]-1; break; end
118 | s += as[j]
119 | end
120 | x = as[j] += 1
121 | for k = j-1:-1:2
122 | as[k] = x
123 | s -= x
124 | end
125 | as[1] = s
126 | end
127 |
128 | return as
129 | end
130 |
131 | let _nipartitions = Dict{Tuple{Int,Int},Int}()
132 | global npartitions
133 | function npartitions(n::Int,m::Int)
134 | if n < m || m == 0
135 | 0
136 | elseif n == m
137 | 1
138 | elseif (np = get(_nipartitions, (n,m), 0)) > 0
139 | np
140 | else
141 | _nipartitions[(n,m)] = npartitions(n-1,m-1) + npartitions(n-m,m)
142 | end
143 | end
144 | end
145 |
146 | # Algorithm H from TAoCP 7.2.1.5
147 | # Set partitions
148 |
149 | immutable SetPartitions{T<:AbstractVector}
150 | s::T
151 | end
152 |
153 | length(p::SetPartitions) = nsetpartitions(length(p.s))
154 |
155 | """
156 | Generate all set partitions of the elements of an array, represented as arrays of arrays. Because the number of partitions can be very large, this function returns an iterator object. Use `collect(partitions(array))` to get an array of all partitions. The number of partitions to generate can be efficiently computed using `length(partitions(array))`.
157 | """
158 | partitions(s::AbstractVector) = SetPartitions(s)
159 |
160 | start(p::SetPartitions) = (n = length(p.s); (zeros(Int32, n), ones(Int32, n-1), n, 1))
161 | done(p::SetPartitions, s) = s[1][1] > 0
162 | next(p::SetPartitions, s) = nextsetpartition(p.s, s...)
163 |
164 | function nextsetpartition(s::AbstractVector, a, b, n, m)
165 | function makeparts(s, a, m)
166 | temp = [ similar(s,0) for k = 0:m ]
167 | for i = 1:n
168 | push!(temp[a[i]+1], s[i])
169 | end
170 | filter!(x->!isempty(x), temp)
171 | end
172 |
173 | if isempty(s); return ([s], ([1], Int[], n, 1)); end
174 |
175 | part = makeparts(s,a,m)
176 |
177 | if a[end] != m
178 | a[end] += 1
179 | else
180 | local j
181 | for j = n-1:-1:1
182 | if a[j] != b[j]
183 | break
184 | end
185 | end
186 | a[j] += 1
187 | m = b[j] + (a[j] == b[j])
188 | for k = j+1:n-1
189 | a[k] = 0
190 | b[k] = m
191 | end
192 | a[end] = 0
193 | end
194 |
195 | return (part, (a,b,n,m))
196 |
197 | end
198 |
199 | let _nsetpartitions = Dict{Int,Int}()
200 | global nsetpartitions
201 | function nsetpartitions(n::Int)
202 | if n < 0
203 | 0
204 | elseif n < 2
205 | 1
206 | elseif (wn = get(_nsetpartitions, n, 0)) > 0
207 | wn
208 | else
209 | wn = 0
210 | for k = 0:n-1
211 | wn += binomial(n-1,k)*nsetpartitions(n-1-k)
212 | end
213 | _nsetpartitions[n] = wn
214 | end
215 | end
216 | end
217 |
218 | immutable FixedSetPartitions{T<:AbstractVector}
219 | s::T
220 | m::Int
221 | end
222 |
223 | length(p::FixedSetPartitions) = nfixedsetpartitions(length(p.s),p.m)
224 |
225 | """
226 | Generate all set partitions of the elements of an array 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(array,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(array,m))`.
227 | """
228 | partitions(s::AbstractVector,m::Int) = length(s) >= 1 && m >= 1 ? FixedSetPartitions(s,m) : throw(DomainError())
229 |
230 | function start(p::FixedSetPartitions)
231 | n = length(p.s)
232 | m = p.m
233 | m <= n ? (vcat(ones(Int, n-m),1:m), vcat(1,n-m+2:n), n) : (Int[], Int[], n)
234 | end
235 | # state consists of:
236 | # vector a of length n describing to which partition every element of s belongs
237 | # vector b of length n describing the first index b[i] that belongs to partition i
238 | # integer n
239 |
240 | done(p::FixedSetPartitions, s) = isempty(s[1]) || s[1][1] > 1
241 | next(p::FixedSetPartitions, s) = nextfixedsetpartition(p.s,p.m, s...)
242 |
243 | function nextfixedsetpartition(s::AbstractVector, m, a, b, n)
244 | function makeparts(s, a)
245 | part = [ similar(s,0) for k = 1:m ]
246 | for i = 1:n
247 | push!(part[a[i]], s[i])
248 | end
249 | return part
250 | end
251 |
252 | part = makeparts(s,a)
253 |
254 | if m == 1
255 | a[1] = 2
256 | return (part, (a, b, n))
257 | end
258 |
259 | if a[end] != m
260 | a[end] += 1
261 | else
262 | local j, k
263 | for j = n-1:-1:1
264 | if a[j]1
269 | a[j]+=1
270 | for p=j+1:n
271 | if b[a[p]]!=p
272 | a[p]=1
273 | end
274 | end
275 | else
276 | for k=m:-1:2
277 | if b[k-1]= x
306 | #for integer n1, n2, n3
307 | #"""
308 | #function nextprod(a::Vector{Int}, x)
309 | # if x > typemax(Int)
310 | # throw(ArgumentError("unsafe for x > typemax(Int), got $x"))
311 | # end
312 | # k = length(a)
313 | # v = ones(Int, k) # current value of each counter
314 | # mx = [nextpow(ai,x) for ai in a] # maximum value of each counter
315 | # v[1] = mx[1] # start at first case that is >= x
316 | # p::widen(Int) = mx[1] # initial value of product in this case
317 | # best = p
318 | # icarry = 1
319 | #
320 | # while v[end] < mx[end]
321 | # if p >= x
322 | # best = p < best ? p : best # keep the best found yet
323 | # carrytest = true
324 | # while carrytest
325 | # p = div(p, v[icarry])
326 | # v[icarry] = 1
327 | # icarry += 1
328 | # p *= a[icarry]
329 | # v[icarry] *= a[icarry]
330 | # carrytest = v[icarry] > mx[icarry] && icarry < k
331 | # end
332 | # if p < x
333 | # icarry = 1
334 | # end
335 | # else
336 | # while p < x
337 | # p *= a[1]
338 | # v[1] *= a[1]
339 | # end
340 | # end
341 | # end
342 | # best = mx[end] < best ? mx[end] : best
343 | # return Int(best) # could overflow, but best to have predictable return type
344 | #end
345 |
346 | doc"""
347 | Previous integer not greater than `n` that can be written as $\prod k_i^{p_i}$ for integers $p_1$, $p_2$, etc.
348 |
349 | For a list of integers i1, i2, i3, find the largest
350 | i1^n1 * i2^n2 * i3^n3 <= x
351 | for integer n1, n2, n3
352 | """
353 | function prevprod(a::Vector{Int}, x)
354 | if x > typemax(Int)
355 | throw(ArgumentError("unsafe for x > typemax(Int), got $x"))
356 | end
357 | k = length(a)
358 | v = ones(Int, k) # current value of each counter
359 | mx = [nextpow(ai,x) for ai in a] # allow each counter to exceed p (sentinel)
360 | first = Int(prevpow(a[1], x)) # start at best case in first factor
361 | v[1] = first
362 | p::widen(Int) = first
363 | best = p
364 | icarry = 1
365 |
366 | while v[end] < mx[end]
367 | while p <= x
368 | best = p > best ? p : best
369 | p *= a[1]
370 | v[1] *= a[1]
371 | end
372 | if p > x
373 | carrytest = true
374 | while carrytest
375 | p = div(p, v[icarry])
376 | v[icarry] = 1
377 | icarry += 1
378 | p *= a[icarry]
379 | v[icarry] *= a[icarry]
380 | carrytest = v[icarry] > mx[icarry] && icarry < k
381 | end
382 | if p <= x
383 | icarry = 1
384 | end
385 | end
386 | end
387 | best = x >= p > best ? p : best
388 | return Int(best)
389 | end
390 |
391 |
392 | "Lists the partitions of the number n, the order is consistent with GAP"
393 | function integer_partitions(n::Integer)
394 | if n < 0
395 | throw(DomainError())
396 | elseif n == 0
397 | return Vector{Int}[]
398 | elseif n == 1
399 | return Vector{Int}[[1]]
400 | end
401 |
402 | list = Vector{Int}[]
403 |
404 | for p in integer_partitions(n-1)
405 | push!(list, [p; 1])
406 | if length(p) == 1 || p[end] < p[end-1]
407 | push!(list, [p[1:end-1]; p[end]+1])
408 | end
409 | end
410 |
411 | list
412 | end
413 |
414 |
415 |
416 | #Noncrossing partitions
417 |
418 | #Produces noncrossing partitions of length n
419 | function ncpartitions(n::Int)
420 | partitions = Vector{Vector{Int}}[]
421 | _ncpart!(1,n,n,Vector{Int}[], partitions)
422 | partitions
423 | end
424 |
425 | function _ncpart!(a::Int, b::Int, nn::Int,
426 | x::Vector, partitions::Vector)
427 |
428 | n=b-a+1
429 | for k=1:n, root in CoolLexCombinations(n, k)
430 | root += a-1
431 | #Abort if construction is out of lex order
432 | if !isempty(x) && lexcmp(x[end], root)==1 return end
433 |
434 | #Save if we've filled all the holes
435 | sofar = Vector{Int}[x..., root]
436 | ssofaru = sort(union(sofar...))
437 | if length(ssofaru)==nn && ssofaru==collect(1:nn)
438 | push!(partitions, sofar)
439 | return
440 | end
441 |
442 | #otherwise patch all remaining holes
443 | blob = [ssofaru; nn+1]
444 | for l=1:length(blob)-1
445 | ap, bp = blob[l]+1, blob[l+1]-1
446 | if ap <= bp _ncpart!(ap, bp, nn, sofar, partitions) end
447 | end
448 | end
449 | end
450 |
451 |
--------------------------------------------------------------------------------