Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add subsets(collection,Val{k}) #13

Merged
merged 17 commits into from
Mar 7, 2018
57 changes: 57 additions & 0 deletions src/IterTools.jl
Original file line number Diff line number Diff line change
Expand Up @@ -708,6 +708,63 @@ end
done(it::Binomial, state::BinomialIterState) = state.done


# Iterate over all subsets of a collection with a given *statically* known size

struct StaticSizeBinomial{K,Container}
xs::Container
end

iteratorsize(::Type{<:StaticSizeBinomial}) = HasLength()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Only if the container has length; see the Subsets type. Also define iteratoreltype as this will only have eltype if Container has eltype.

Copy link
Contributor Author

@ettersi ettersi Nov 21, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was following the implementation of Binomial, which has the same issues. The point with iteratoreltype is fair enough and should be changed for all three cases of subsets. All three implementations require length(xs), though, so all subsets iterators have a length whenever they're defined.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems several iterators in this package do not define iteratoreltype correctly. So it might be worth opening up another PR to fix this for all.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Binomial doesn't have the same issues, as it only accepts a Vector which always has an eltype and length.

Base.iteratoreltype(::Type{StaticSizeBinomial{K,C}}) where {K,C} = iteratoreltype(C)

eltype(::Type{StaticSizeBinomial{K,C}}) where {K,C} = NTuple{K,eltype(C)}
length(it::StaticSizeBinomial{K,<:Any}) where {K} = binomial(length(it.xs),K)

subsets(xs,::Val{K}) where {K} = StaticSizeBinomial{K,typeof(xs)}(xs)

@generated minus1(::Val{A}) where {A} = :(Val{$(A-1)}())
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Jameson has said "no" to this in the past. However, you can use first and Base.tail for inferred head and tail on tuples.

Copy link
Contributor Author

@ettersi ettersi Nov 24, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Replaced pop() with

@inline pop(t::NTuple) = reverse(Base.tail(reverse(t))), t[end]

Performance seems to stay the same. Don't really see the problem with the old code, though.

pop(t::NTuple{K,<:Any}) where {K} = ntuple(i->t[i], minus1(Val{K}()))

function start(it::StaticSizeBinomial{K,<:Any}) where {K}
xs = it.xs
li = ntuple(identity,minus1(Val{K}()))
lx = ntuple(i->xs[i],minus1(Val{K}()))
return lx, li, li[end]+1
end

function advance(it::StaticSizeBinomial{K,<:Any}, xx,ii) where {K}
xs = it.xs
lx = pop(xx)
li = pop(ii)
i = ii[end] + 1
if i > length(xs)-K+length(ii)
lx,li = advance(it,lx,li)
i = li[end]+1
end
return (lx...,xs[i]),(li...,i)
end
function advance(it::StaticSizeBinomial,x::NTuple{1,<:Any},i::NTuple{1,<:Any})
xs = it.xs
i = i[end]+1
return (xs[i],),(i,)
end

function next(it::StaticSizeBinomial,state)
xs = it.xs
lx,li,i = state
x = (lx...,xs[i])

i += 1
if i > length(xs)
lx,li = advance(it,lx,li)
i = li[end]+1
end
return x,(lx,li,i)
end

done(it::StaticSizeBinomial,state) = state[end] > length(it.xs)


# nth : return the nth element in a collection

"""
Expand Down
46 changes: 46 additions & 0 deletions test/runtests.jl
Original file line number Diff line number Diff line change
Expand Up @@ -282,6 +282,52 @@ include("testing_macros.jl")
@test length(collect(sk11)) == binomial(3, 2)
end
end

@testset "specific static length" begin
sk0 = subsets([:a, :b, :c],Val{0}())
@test collect(sk0) == [()]

sk1 = subsets([:a, :b, :c], Val{1}())
@test eltype(eltype(sk1)) == Symbol
@test collect(sk1) == [(:a,), (:b,), (:c,)]

sk2 = subsets([:a, :b, :c], Val{2}())
@test eltype(eltype(sk2)) == Symbol
@test collect(sk2) == [(:a,:b), (:a,:c), (:b,:c)]

sk3 = subsets([:a, :b, :c], Val{3}())
@test eltype(eltype(sk3)) == Symbol
@test collect(sk3) == [(:a,:b,:c)]

sk4 = subsets([:a, :b, :c], Val{4}())
@test eltype(eltype(sk4)) == Symbol
@test collect(sk4) == []

sk5 = subsets([:a, :b, :c], Val{5}())
@test eltype(eltype(sk5)) == Symbol
@test collect(sk5) == []

@testset for i in 1:6
sk5 = subsets(collect(1:4), Val{i}())
@test eltype(eltype(sk5)) == Int
@test length(collect(sk5)) == binomial(4, i)
end

function collect_pairs(x)
p = Vector{NTuple{2,eltype(x)}}(binomial(length(x),2))
idx = 1
for i = 1:length(x)
for j = i+1:length(x)
p[idx] = (x[i],x[j])
idx += 1
end
end
return p
end
@testset for n = 1:10
@test collect(subsets(1:n,Val{2}())) == collect_pairs(1:n)
end
end
end

@testset "nth" begin
Expand Down