I have a basic for-loop that is looping over a vector, and I would like to check if the loop iteration is at the end of vector. Are there any recommended ways of doing this idiomatically? Right now I have a basic enumerate
and if index != length(my_vector)
.
for (index, element) in enumerate(my_vector)
do_stuff(element)
if index != length(my_vector)
do_more_stuff()
end
end
you could iterate on a view of that vector , @view(vector[begin:end-1])
Isn't it preferable in terms of performance to treat your_vector [1: end-1] and your_vector [end] separately?
for (index, element) in enumerate(my_vector[1:end-1])
do_stuff(element)
end
do_more_stuff(my_vector[end])
You better use pairs
instead of enumerate
.
Andrey Oskin said:
You better use
pairs
instead ofenumerate
.
Could you elaborate more on any reasons in particular (e.g. performance, readability, etc.)? I just tried it and I did not know that pairs(my_vector)
gives a nice indexing similar to enumerate
. So thank you for that!
rocco sprmnt21 said:
Isn't it preferable in terms of performance to treat your_vector [1: end-1] and your_vector [end] separately?
for (index, element) in enumerate(my_vector[1:end-1]) do_stuff(element) end do_more_stuff(my_vector[end])
I'm actually really not sure! Do you know why that might be the case?
Andrés Riedemann said:
you could iterate on a view of that vector ,
@view(vector[begin:end-1])
Oh interesting! Thank you. What's the advantage of iterating on a view?
Kim Paolo Laberinto said:
Andrey Oskin said:
You better use
pairs
instead ofenumerate
.Could you elaborate more on any reasons in particular (e.g. performance, readability, etc.)? I just tried it and I did not know that
pairs(my_vector)
gives a nice indexing similar toenumerate
. So thank you for that!
enumerate
always counts from 1
. It doesn't care about special index styles which can lead to bugs
e.g.
julia> using OffsetArrays
julia> v = OffsetArray(1:5, -2:2)
1:5 with indices -2:2
julia> collect(enumerate(v))
5-element Vector{Tuple{Int64, Int64}}:
(1, 1)
(2, 2)
(3, 3)
(4, 4)
(5, 5)
julia> collect(pairs(v))
5-element OffsetArray(::Vector{Pair{Int64, Int64}}, -2:2) with eltype Pair{Int64, Int64} with indices -2:2:
-2 => 1
-1 => 2
0 => 3
1 => 4
2 => 5
Or more clearly:
julia> for (i, vi) in enumerate(v)
@show vi == v[i]
end
vi == v[i] = false
vi == v[i] = false
ERROR: BoundsError: attempt to access 5-element OffsetArray(::UnitRange{Int64}, -2:2) with eltype Int64 with indices -2:2 at index [3]
Stacktrace:
[1] throw_boundserror(A::OffsetVector{Int64, UnitRange{Int64}}, I::Tuple{Int64})
@ Base ./abstractarray.jl:691
[2] checkbounds
@ ./abstractarray.jl:656 [inlined]
[3] getindex(A::OffsetVector{Int64, UnitRange{Int64}}, i::Int64)
@ OffsetArrays ~/.julia/packages/OffsetArrays/TKbp1/src/OffsetArrays.jl:424
[4] macro expansion
@ ./show.jl:1040 [inlined]
[5] top-level scope
@ ./REPL[14]:2
@Mason Protter Thank you!! This really helps my understanding. Then it seems like the method of using enumerate(my_vector)
with if index != length(my_vector)
is a good enough way of doing things and using pairs
might give me something I didn't expect. TIL that enumerate
always counts from 1
, and should be resilient against OffsetArrays. Thank you!!
julia> v = OffsetArray(101:105, -2:2)
101:105 with indices -2:2
julia> for (i, e) in enumerate(v)
print(i, " ", e)
if i != length(v)
println(" not yet at the end")
end
end
1 101 not yet at the end
2 102 not yet at the end
3 103 not yet at the end
4 104 not yet at the end
5 105
julia> for (i, e) in pairs(v)
print(i, " ", e)
if i != length(v)
println(" not yet at the end")
end
end
-2 101 not yet at the end
-1 102 not yet at the end
0 103 not yet at the end
1 104 not yet at the end
2 105 not yet at the end
You could also use the lastindex
function with pairs
. i.e.
julia> for (i, e) in pairs(v)
print(i, " ", e)
if i != lastindex(v)
println(" not yet at the end")
end
end
-2 101 not yet at the end
-1 102 not yet at the end
0 103 not yet at the end
1 104 not yet at the end
2 105
Kim Paolo Laberinto said:
rocco sprmnt21 said:
Isn't it preferable in terms of performance to treat your_vector [1: end-1] and your_vector [end] separately?
for (index, element) in enumerate(my_vector[1:end-1]) do_stuff(element) end do_more_stuff(my_vector[end])
I'm actually really not sure! Do you know why that might be the case?
Because as I suggest, you avoid checking each iteration if you have reached the last index in order to apply the specific function of the case.
If you are not convinced run the following tests, perhaps adapting them to your specific case and let us know.
my_vector=1:10^6
do_stuff(e)=e
do_more_stuff(e)=e*10
function f(arr)
for (index, element) in enumerate(arr[1:end-1])
do_stuff(element)
end
do_more_stuff(arr[end])
end
function g(arr)
for (i, v) in pairs(arr)
do_stuff(v)
if i == lastindex(arr)
return do_more_stuff(v)
end
end
end
g(my_vector)==f(my_vector)
using BenchmarkTools
@btime g(my_vector)
@btime f(my_vector)
Oh I see what you mean now @rocco sprmnt21 ! That makes sense to me intuitively: instead of doing a conditional check during the for loop for every single element, just do the unique behavior at the end so that the checks don't need to happen. Thank you!
Last updated: Nov 06 2024 at 04:40 UTC