Skip to content

Commit 2381c5b

Browse files
authored
Optimize permutations by implementing it via multiset_permutations (#186)
* perf: optimize `permutations` by implementing it via `multiset_permutations` As it currently stands, `multiset_permutations` is more efficient than `permutations`; see #151. We can exploit it to optimize `permutations`. * fix: use explicit NamedTuple notation tu support Julia 1.0 * fix: don't use `isnothing` to support Julia 1.0 * fix: collect the indices into a `Vector` Without this fix there are issues with `OffsetArray`: `eachindex` returns an `OffsetArrays.IdOffsetRange`, which causes troubles when passed to `multiset_permutations` because `f = [sum(c == x for c in a)::Int for x in m]` becomes an `OffsetArray` instead of a Vector`.
1 parent 0ab0915 commit 2381c5b

File tree

1 file changed

+19
-37
lines changed

1 file changed

+19
-37
lines changed

src/permutations.jl

Lines changed: 19 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -15,44 +15,26 @@ struct Permutations{T}
1515
length::Int
1616
end
1717

18-
function has_repeats(state::Vector{Int})
19-
# This can be safely marked inbounds because of the type restriction in the signature.
20-
# If the type restriction is ever loosened, please check safety of the `@inbounds`.
21-
@inbounds for outer in eachindex(state)
22-
for inner in (outer+1):lastindex(state)
23-
if state[outer] == state[inner]
24-
return true
25-
end
26-
end
27-
end
28-
return false
29-
end
30-
31-
function increment!(state::Vector{Int}, min::Int, max::Int)
32-
# All array indexing can be marked inbounds because of the type restriction in the signature.
33-
# If the type restriction is ever loosened, please check safety of the `@inbounds`.
34-
@inbounds state[end] += 1
35-
i = lastindex(state)
36-
@inbounds while i > firstindex(state) && state[i] > max
37-
state[i] = min
38-
state[i-1] += 1
39-
i -= 1
40-
end
41-
end
42-
43-
function next_permutation!(state::Vector{Int}, min::Int, max::Int)
44-
while true
45-
increment!(state, min, max)
46-
has_repeats(state) || break
47-
end
48-
end
49-
50-
function Base.iterate(p::Permutations, state::Vector{Int}=fill(firstindex(p.data), p.length))
51-
next_permutation!(state, firstindex(p.data), lastindex(p.data))
52-
if first(state) > lastindex(p.data)
53-
return nothing
18+
# The following code basically implements `permutations` in terms of `multiset_permutations` as
19+
#
20+
# permutations(a, t::Integer=length(a)) = Iterators.map(
21+
# indices -> [a[i] for i in indices],
22+
# multiset_permutations(eachindex(a), t))
23+
#
24+
# with the difference that we can also define `eltype(::Permutations)`, which is used in some tests.
25+
26+
function Base.iterate(p::Permutations, state=nothing)
27+
if state === nothing
28+
mp = multiset_permutations(collect(eachindex(p.data)), p.length)
29+
it = iterate(mp)
30+
if it === nothing return nothing end
31+
else
32+
mp, mp_state = state
33+
it = iterate(mp, mp_state)
34+
if it === nothing return nothing end
5435
end
55-
[p.data[i] for i in state], state
36+
indices, mp_state = it
37+
return [p.data[i] for i in indices], (mp=mp, mp_state=mp_state)
5638
end
5739

5840
function Base.length(p::Permutations)

0 commit comments

Comments
 (0)