Skip to content

Commit e005ee0

Browse files
authored
Add new function derangements (#150)
* Add a function to generate all derangements. * Add tests for `derangements` * Add doctest for `derangements` * Fix type stability in `multiset_permutations` * Remove unnecessary call to `collect`
1 parent edb9c2c commit e005ee0

File tree

2 files changed

+51
-2
lines changed

2 files changed

+51
-2
lines changed

src/permutations.jl

Lines changed: 39 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
#Permutations
22

33
export
4+
derangements,
45
levicivita,
56
multiset_permutations,
67
nthperm!,
@@ -136,6 +137,42 @@ function permutations(a, t::Integer)
136137
return Permutations(a, t)
137138
end
138139

140+
"""
141+
derangements(a)
142+
143+
Generate all derangements of an indexable object `a` in lexicographic order.
144+
Because the number of derangements can be very large, this function returns an iterator object.
145+
Use `collect(derangements(a))` to get an array of all derangements.
146+
Only works for `a` with defined length.
147+
148+
# Examples
149+
```jldoctest
150+
julia> derangements("julia") |> collect
151+
44-element Vector{Vector{Char}}:
152+
['u', 'j', 'i', 'a', 'l']
153+
['u', 'j', 'a', 'l', 'i']
154+
['u', 'l', 'j', 'a', 'i']
155+
['u', 'l', 'i', 'a', 'j']
156+
['u', 'l', 'a', 'j', 'i']
157+
['u', 'i', 'j', 'a', 'l']
158+
['u', 'i', 'a', 'j', 'l']
159+
['u', 'i', 'a', 'l', 'j']
160+
['u', 'a', 'j', 'l', 'i']
161+
['u', 'a', 'i', 'j', 'l']
162+
163+
['a', 'j', 'i', 'l', 'u']
164+
['a', 'l', 'j', 'u', 'i']
165+
['a', 'l', 'u', 'j', 'i']
166+
['a', 'l', 'i', 'j', 'u']
167+
['a', 'l', 'i', 'u', 'j']
168+
['a', 'i', 'j', 'u', 'l']
169+
['a', 'i', 'j', 'l', 'u']
170+
['a', 'i', 'u', 'j', 'l']
171+
['a', 'i', 'u', 'l', 'j']
172+
```
173+
"""
174+
derangements(a) = (d for d in multiset_permutations(a, length(a)) if all(t -> t[1] != t[2], zip(a, d)))
175+
139176

140177
function nextpermutation(m, t, state)
141178
perm = [m[state[i]] for i in 1:t]
@@ -249,8 +286,8 @@ julia> collect(multiset_permutations([1,1,2], 3))
249286
```
250287
"""
251288
function multiset_permutations(a, t::Integer)
252-
m = unique(collect(a))
253-
f = [sum([c == x for c in a]) for x in m]
289+
m = unique(a)
290+
f = [sum(c == x for c in a)::Int for x in m]
254291
multiset_permutations(m, f, t)
255292
end
256293

test/permutations.jl

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,18 @@ end
6868
@test collect(multiset_permutations("", -1)) == Any[]
6969
@test length(multiset_permutations("aaaaaaaaaaaaaaaaaaaaab", 21)) == 22
7070

71+
# derangements
72+
@test length(collect(derangements(1:4))) == 9
73+
@test length(collect(derangements(1:8))) == derangement(8) == 14833
74+
@test collect(derangements([])) == [[]]
75+
@test collect(derangements(Int[])) == [Int[]]
76+
@test collect(derangements([1])) == Vector{Int}[]
77+
@test collect(derangements([1, 1])) == Vector{Int}[]
78+
@test collect(derangements([1, 1, 2])) == Vector{Int}[]
79+
@test collect(derangements([1, 1, 2, 2])) == [[2, 2, 1, 1]]
80+
@test map(join, derangements("aabbc")) == ["bbaca", "bbcaa", "bcaab", "cbaab"]
81+
@test map(join, derangements("aaabbbc")) == ["bbbaaca", "bbbacaa", "bbbcaaa", "bbcaaab", "bcbaaab", "cbbaaab"]
82+
7183
#nthperm!
7284
for n = 0:7, k = 1:factorial(n)
7385
p = nthperm!([1:n;], k)

0 commit comments

Comments
 (0)