Filter! vs filter

Why is the mutating version of filter failing in this example?

limit = 10
marked = 2
candidates = marked:limit
multiples = [marked * i for i in 2:fld(limit, marked)]
filter(x -> x in multiples, candidates)

gives

4-element Array{Int64,1}:
  4
  6
  8
 10

but

filter!(x -> x in multiples, candidates)

gives

setindex! not defined for UnitRange{Int64}

Stacktrace:
 [1] error(::String, ::Type) at ./error.jl:42
 [2] error_if_canonical_setindex(::IndexLinear, ::UnitRange{Int64}, ::Int64) at ./abstractarray.jl:1082
 [3] setindex! at ./abstractarray.jl:1073 [inlined]
 [4] filter!(::var"#25#26", ::UnitRange{Int64}) at ./array.jl:2390
 [5] top-level scope at In[24]:1

The error is telling you why: setindex! is not defined for UnitRange objects, that is you can’t change a single element in a UnitRange

The reason for this is that UnitRange is an efficient iterator, that doesn’t actually store all of the elements in the range, but just the starting point, end point, and step size. If you want to alter individual elements of your range, you should collect() your range into a vector.

4 Likes

Thanks for spelling this out and the fix!

I wrongly assumed that the a:b shorthand ends up being the same type as the corresponding array. The visual difference of the representation in the output of the shorthand definition vs. an array should have been another clue that those are really two different types.

2 Likes

Keep in mind that you really only need to collect ranges like 1:n when you need to modify them, otherwise it’s better to just use them as is, since they behave just like vectors. Too many new users collect them needlessly.

2 Likes

You can still use candidates to refer to the new filtered data without loosing any performance using Julia’s powerful Iterators. In this case, you don’t have to materialize and allocate new memory.

julia> limit = 10;
julia> marked = 2;
julia> candidates = marked:limit;
julia> multiples = [marked * i for i in 2:fld(limit, marked)];

julia> candidates = Iterators.Filter(in(multiples), candidates)
Base.Iterators.Filter{Base.Fix2{typeof(in),Array{Int64,1}},UnitRange{Int64}}(Base.Fix2{typeof(in),Array{Int64,1}}(in, [4, 6, 8, 10]), 2:10)

julia> println.(candidates);
4
6
8
10

julia> println.(2 .* candidates);
8
12
16
20