Stream: helpdesk (published)

Topic: Check if loop is at end of vector idiomatically


view this post on Zulip Kim Paolo Laberinto (Sep 03 2021 at 05:49):

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

view this post on Zulip Andrés Riedemann (Sep 03 2021 at 05:51):

you could iterate on a view of that vector , @view(vector[begin:end-1])

view this post on Zulip rocco sprmnt21 (Sep 03 2021 at 07:11):

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])

view this post on Zulip Kwaku Oskin (Sep 03 2021 at 08:28):

You better use pairs instead of enumerate.

view this post on Zulip Kim Paolo Laberinto (Sep 03 2021 at 18:13):

Andrey Oskin said:

You better use pairs instead of enumerate.

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!

view this post on Zulip Kim Paolo Laberinto (Sep 03 2021 at 18:14):

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?

view this post on Zulip Kim Paolo Laberinto (Sep 03 2021 at 18:15):

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?

view this post on Zulip Mason Protter (Sep 03 2021 at 18:18):

Kim Paolo Laberinto said:

Andrey Oskin said:

You better use pairs instead of enumerate.

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!

enumerate always counts from 1. It doesn't care about special index styles which can lead to bugs

view this post on Zulip Mason Protter (Sep 03 2021 at 18:20):

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

view this post on Zulip Mason Protter (Sep 03 2021 at 18:21):

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

view this post on Zulip Kim Paolo Laberinto (Sep 03 2021 at 18:33):

@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

view this post on Zulip Mason Protter (Sep 03 2021 at 18:58):

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

view this post on Zulip rocco sprmnt21 (Sep 03 2021 at 19:42):

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)

view this post on Zulip Kim Paolo Laberinto (Sep 03 2021 at 19:48):

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