Почему это не допустимый итератор таблицы?
Я видел этот пример в уроке lua:
function fromto(a, b)
return
function(state, seed)
if (seed >= state) then return nil
else return seed+1 end
end, b, a-1
end
Он возвращает следующие целочисленные значения от a до b включительно. Поэтому я попытался применить ту же логику, написав следующий итератор таблицы:
function values(t) -- t is a table
return
function(state, seed)
return state[seed+1]
end, t, 0
end
Он возвращает первое значение правильно, но затем выдает ошибку, говоря, что я не могу делать арифметику в строковом значении (seed
). Но разве seed
не получает значение 0
, которое является целым числом? Что происходит?
Поскольку нет процесса инкрементирования на первом например (например, a = a + 1
), я думаю, что Луа может справиться с этим за кулисами... но если это не так, возможно, это и есть причина моего замешательства.
2 ответа:
seed
это плохое имя для второго параметра вашей итерационной функции. На самом деле это управляющая переменная. Вы "затравляете" его, когда возвращаете 0 изvalues
, но после этого его значение изменяется во время цикла: оно принимает первое значение, возвращенное из функции итератора.Вы вернули строку из итератора (
return state[seed+1]
), поэтому при следующем вызове функции итератора эта строка была передана. Вы пытались сделать математику на нем, и... БАМ.Универсальный
for
принимает три параметра: функцию итератора, инвариантное состояние и начальное управляющее значение. Итератор вызывается с состоянием и значением элемента управления. Затем итератор возвращает следующее управляющее значение илиnil
, чтобы указать, что итерация выполнена.Таким образом, первый вызовt = {"foo","bar","zip","zap"} local function iteratorFunction (state, index) index = index + 1 local val = state[index] if val == nil then return nil end return index, val end for k, v in iteratorFunction, t, 0 do print(k,v) end
iteratorFunction
получаетt
и0
в качестве параметров. следующий вызовiteratorFunction
получаетt
и первое значение, возвращенное изiteratorFunction
, ну и так далее и так далее.Когда вы пишете функцию "генератор", такую как
values
, Вы просто возвращаете три начальных значения, требуемые циклом generic for, чтобы ваш код был более лаконичным при использовании этого итератора:function values(t) local function iteratorFunction (state, index) index = index + 1 local val = state[index] if val == nil then return nil end return index, val end return iteratorFunction, t, 0 -- the same three values used in the for loop above end for k, v in values(t) do print(k,v) end
Единственным обязательным параметром для generic
for
является функция итератора. Инвариантное состояние и управляющая переменная могут быть равны нулю, что можно сделать, если ваша итерационная логика выполняется в закрытии:function values(t) local index = 0 -- our iterator function is a closure bound to `index` and `t` local function iteratorFunction() index = index + 1 local val = t[index] if val == nil then return nil end return index, val end return iteratorFunction, t, 0 -- the same three values used in the for loop above end for k, v in values(t) do print(k,v) end
Если вы измените
return index, val
на простоreturn val
,values(t)
будет сейчас перебирайте только значения вt
. Мы не могли сделать этого раньше, потому что нам нужно было вернуть управляющую переменную для следующей итерации цикла. При закрытии мы поддерживаем управляющую переменную через переменную, связанную с закрытием (она же "upvalue").
Можно использовать другую форму итераторов с замыканиями:
local function values(t) local i = 0 return function() i = i + 1 return t[i] end end for x in values({1, 2, 3}) do print(x) end
1 2 3
Итератор продолжается до тех пор, пока не будет возвращен
nil
. Недопустимое поле таблицы (за последним) всегда равно нулю, поэтому вам просто нужно продолжать идти.Форма с передаваемыми значениями счетчика в вашем примере использует возвращаемое значение для обновления последнего значения счетчика и обычно не используется. Вы обновляете
seed
с вашей записью таблицы, которая является строкой, поэтому следующий проход должен завершиться ошибкой.Кстати.: твой значения функция делает то же самое, что и
ipairs
.