How to detect the last iteration in a loop

How do I detect the last iteration in a loop, e.g. generation INSERT INTO statement for several records:

sql = "INSERT INTO table VALUES "
for oneRecord in records # guaranteed that records is not empty
    sql *= "($val1, $val2, $oneRecord)"
    if islast # this statement I am asking about
        sql *= ";"
    else
        sql *= ", "
    end
end

I know I could use lenght and count iteration, but I think there must be something nicer.

Thank you,

I wonder whether something like this might help:

records = Iterators.Stateful(1:10)
for i in records
    if peek(records) == nothing
        println(" <- that was the penultimate value")
    end
    println(i)
end

1
2
3
4
5
6
7
8
9
 <- that was the penultimate value
10
2 Likes

The idiom I like to use to solve this particular problem is to use join. join can be used to join together an Array of strings with a chosen separator. E.g:

sql = "INSERT INTO table VALUES "

inserts = ["($val1, $val2, $oneRecord)" for oneRecord in records]

sql *= join(inserts, ", ")

I’ve used an array comprehension to create a temporary Array inserts that have the nicely formatted strings you want to join together

(This does not answer your actual question of how to detect the last iteration: it’s a different way of solving the question in your code - which is a pattern I came across in my own work all the time.)

2 Likes

Suggested code works with UInt8, but when I use strings, as it is in my case:

records=["a", "b", "c"]

I am getting:

ERROR: MethodError: no method matching peek(::Array{String,1}, ::Type{UInt8})
Closest candidates are:
  peek(::Base.Iterators.Stateful, ::Any) at iterators.jl:1274
  peek(::REPL.Terminals.TTYTerminal, ::Type{T}) where T at C:\buildbot\worker\package_win64\build\usr\share\julia\stdlib\v1.5\REPL\src\Terminals.jl:163
  peek(::Base.AbstractPipe, ::Type{T}) where T at io.jl:358

There is no way, in general, to know that an iteration is done until you actually get nothing back from iterate So you need to remember two values while iterating. This is how join does it:

5 Likes

Thank you, @angusmoore , that’s exactly what I am going to use.

1 Like

You can do it in a different order

sql = "INSERT INTO table VALUES "
for (i, oneRecord) in enumerate(records) # guaranteed that records is not empty
    i != 1 && sql *= ", "
    sql *= "($val1, $val2, $oneRecord)"
end
sql *= ";"

@Skoffer Excellent, thank you!

I would also use join, but if you insist on a loop, you can also check for the first element, and reorganize the logic a bit:

using InterTools
sql = "INSERT INTO table VALUES "
for (isfirst, oneRecord) in IterTools.flagfirst(records)
    if !isfirst
        sql *= ", "
    end
    sql *= "($val1, $val2, $oneRecord)"
end
sql *= ";"

The other solutions look good to me too, but to make the Iterators.Stateful one work, you just need to wrap the iterator (whatever it is, vector of strings or anything else) in Iterators.Stateful, and then just use the wrapper, e.g.

records=["a", "b", "c"]
itr = Iterators.Stateful(records)

for i in itr
    if peek(itr) == nothing
        println(" <- that was the penultimate value")
    end
    println(i)
end

yields

a
b
 <- that was the penultimate value
c

This is just a convienent way of doing the “remember values while iterating” for you.

3 Likes