Skip to content

Commit ebf1319

Browse files
committed
Merge pull request #10 from randy3k/combinperm
four more arrangement generators
2 parents d1b6872 + b6a43fd commit ebf1319

File tree

4 files changed

+267
-24
lines changed

4 files changed

+267
-24
lines changed

src/combinations.jl

+119-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
1-
export combinations, CoolLexCombinations
1+
export combinations,
2+
CoolLexCombinations,
3+
multiset_combinations,
4+
with_replacement_combinations
25

36
#The Combinations iterator
47

@@ -132,3 +135,118 @@ end
132135
done(C::CoolLexCombinations, S::CoolLexIterState) = (S.R3 & S.R2 != 0)
133136

134137
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

src/permutations.jl

+106-21
Original file line numberDiff line numberDiff line change
@@ -2,47 +2,133 @@
22

33
export
44
levicivita,
5+
multiset_permutations,
56
nthperm!,
67
nthperm,
78
parity,
89
permutations
910

10-
#The basic permutations iterator
1111

1212
immutable Permutations{T}
1313
a::T
14+
t::Int
1415
end
1516

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+
1621
"""
1722
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.
1823
"""
19-
permutations(a) = Permutations(a)
20-
21-
eltype{T}(::Type{Permutations{T}}) = Vector{eltype(T)}
24+
permutations(a) = Permutations(a, length(a))
2225

23-
length(p::Permutations) = factorial(length(p.a))
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
2435

2536
start(p::Permutations) = [1:length(p.a);]
26-
function next(p::Permutations, s)
27-
perm = [p.a[si] for si in s]
28-
if isempty(p.a)
29-
# special case to generate 1 result for len==0
30-
return (perm,[1])
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])
3144
end
3245
s = copy(s)
33-
k = length(s)-1
34-
while k > 0 && s[k] > s[k+1]; k -= 1; end
35-
if k == 0
36-
s[1] = length(s)+1 # done
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]
3788
else
38-
l = length(s)
39-
while s[k] >= s[l]; l -= 1; end
40-
s[k],s[l] = s[l],s[k]
41-
reverse!(s,k+1)
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
42107
end
43-
(perm,s)
108+
return round(Int, p[t+1])
44109
end
45-
done(p::Permutations, s) = !isempty(s) && s[1] > length(p.a)
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+
46132

47133

48134
"In-place version of nthperm."
@@ -136,4 +222,3 @@ function parity{T<:Integer}(p::AbstractVector{T})
136222
epsilon == 0 && throw(ArgumentError("Not a permutation"))
137223
epsilon == 1 ? 0 : 1
138224
end
139-

test/combinations.jl

+21-1
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,29 @@ import Combinatorics: combinations
1515

1616
@test collect(filter(x->(iseven(x[1])),combinations([1,2,3],2))) == Any[[2,3]]
1717

18+
# multiset_combinations
19+
@test collect(multiset_combinations("aabc", 5)) == Any[]
20+
@test collect(multiset_combinations("aabc", 2)) == Any[['a','a'],['a','b'],['a','c'],['b','c']]
21+
@test collect(multiset_combinations("aabc", 1)) == Any[['a'],['b'],['c']]
22+
@test collect(multiset_combinations("aabc", 0)) == Any[Char[]]
23+
@test collect(multiset_combinations("aabc", -1)) == Any[]
24+
@test collect(multiset_combinations("", 1)) == Any[]
25+
@test collect(multiset_combinations("", 0)) == Any[Char[]]
26+
@test collect(multiset_combinations("", -1)) == Any[]
27+
28+
# with_replacement_combinations
29+
@test collect(with_replacement_combinations("abc", 2)) == Any[['a','a'],['a','b'],['a','c'],
30+
['b','b'],['b','c'],['c','c']]
31+
@test collect(with_replacement_combinations("abc", 1)) == Any[['a'],['b'],['c']]
32+
@test collect(with_replacement_combinations("abc", 0)) == Any[Char[]]
33+
@test collect(with_replacement_combinations("abc", -1)) == Any[]
34+
@test collect(with_replacement_combinations("", 1)) == Any[]
35+
@test collect(with_replacement_combinations("", 0)) == Any[Char[]]
36+
@test collect(with_replacement_combinations("", -1)) == Any[]
37+
38+
1839
#cool-lex iterator
1940
@test_throws DomainError collect(CoolLexCombinations(-1, 1))
2041
@test_throws DomainError collect(CoolLexCombinations(5, 0))
2142
@test collect(CoolLexCombinations(4,2)) == Vector[[1,2], [2,3], [1,3], [2,4], [3,4], [1,4]]
2243
@test isa(start(CoolLexCombinations(1000, 20)), Combinatorics.CoolLexIterState{BigInt})
23-

test/permutations.jl

+21-1
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ using Base.Test
33

44
import Combinatorics: levicivita, nthperm, nthperm!, parity, permutations
55

6+
# permutations
67
@test collect(permutations("abc")) == Any[['a','b','c'],['a','c','b'],['b','a','c'],
78
['b','c','a'],['c','a','b'],['c','b','a']]
89

@@ -11,6 +12,26 @@ import Combinatorics: levicivita, nthperm, nthperm!, parity, permutations
1112

1213
@test length(permutations(0)) == 1
1314

15+
@test collect(permutations("abc", 4)) == Any[]
16+
@test collect(permutations("abc", 2)) == Any[['a','b'],['a','c'],['b','a'],
17+
['b','c'],['c','a'],['c','b']]
18+
@test collect(permutations("abc", 0)) == Any[Char[]]
19+
@test collect(permutations("abc", -1)) == Any[]
20+
@test collect(permutations("", 1)) == Any[]
21+
@test collect(permutations("", 0)) == Any[Char[]]
22+
@test collect(permutations("", -1)) == Any[]
23+
24+
# multiset_permutations
25+
@test collect(multiset_permutations("aabc", 5)) == Any[]
26+
@test collect(multiset_permutations("aabc", 2)) == Any[['a','a'],['a','b'], ['a','c'],['b','a'],
27+
['b','c'],['c','a'],['c','b']]
28+
@test collect(multiset_permutations("aabc", 0)) == Any[Char[]]
29+
@test collect(multiset_permutations("aabc", -1)) == Any[]
30+
@test collect(multiset_permutations("", 1)) == Any[]
31+
@test collect(multiset_permutations("", 0)) == Any[Char[]]
32+
@test collect(multiset_permutations("", -1)) == Any[]
33+
@test length(multiset_permutations("aaaaaaaaaaaaaaaaaaaaab", 21)) == 22
34+
1435
#nthperm!
1536
for n = 0:7, k = 1:factorial(n)
1637
p = nthperm!([1:n;], k)
@@ -32,4 +53,3 @@ end
3253

3354
@test Combinatorics.nsetpartitions(-1) == 0
3455
@test collect(permutations([])) == Vector[[]]
35-

0 commit comments

Comments
 (0)