Skip to content

Commit e3278db

Browse files
Improve performance of derangement/subfactorial with iterative implementation (#146)
* Improve performance of `derangement`/`subfactorial` with iterative implementation Use the recursive formula !n = (n-1) * (!(n-1) + !(n-2)) presented here: https://en.wikipedia.org/wiki/Derangement#Counting_derangements * Improve performance of `derangement`/`subfactorial` with simpler recursive formula and inplace computations Use the simpler formula !n = n * !(n-1) + (-1)^n and use inplace operations on `BigInt`s to avoid allocations. * Update test/factorials.jl --------- Co-authored-by: Chengyu Han <[email protected]>
1 parent 403dcb4 commit e3278db

File tree

2 files changed

+20
-3
lines changed

2 files changed

+20
-3
lines changed

src/factorials.jl

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -38,9 +38,23 @@ Base.factorial(n::Integer, k::Integer) = factorial(promote(n, k)...)
3838
Compute the number of permutations of `n` with no fixed points, also known as the
3939
subfactorial. An alias `subfactorial` for this function is provided for convenience.
4040
"""
41-
function derangement(sn::Integer)
42-
n = BigInt(sn)
43-
return numerator(factorial(n) * sum([(-1)^k // factorial(k) for k = 0:n]))
41+
function derangement(n::Integer)
42+
if n < 0
43+
throw(DomainError(n, "n must be nonnegative"))
44+
elseif n <= 1
45+
return BigInt(1-n)
46+
end
47+
d = BigInt(0)
48+
for i in 2:n
49+
# d = i * d + (iseven(i) ? 1 : -1)
50+
Base.GMP.MPZ.mul_ui!(d, i)
51+
if iseven(i)
52+
Base.GMP.MPZ.add_ui!(d, 1)
53+
else
54+
Base.GMP.MPZ.sub_ui!(d, 1)
55+
end
56+
end
57+
return d
4458
end
4559
const subfactorial = derangement
4660

test/factorials.jl

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,10 @@
1010

1111
# derangement
1212
@test derangement(4) == subfactorial(4) == 9
13+
@test derangement(0) == 1
14+
@test derangement(1) == 0
1315
@test derangement(24) == parse(BigInt, "228250211305338670494289")
16+
@test_throws DomainError derangement(-1)
1417

1518
# partialderangement
1619
@test partialderangement(7, 3) == 315

0 commit comments

Comments
 (0)