Range Iterator

lua-users home
wiki

The range function below returns an iterator that can be used in for loops instead of the basic for i=i,j,k do end syntax. It can be used for example when the two or three parameters of the basic for loop are returned by another function. -- JeromeVuarand

function range(from, to, step)

  step = step or 1

  return function(_, lastvalue)

    local nextvalue = lastvalue + step

    if step > 0 and nextvalue <= to or step < 0 and nextvalue >= to or

       step == 0

    then

      return nextvalue

    end

  end, nil, from - step

end

Example use:

function f() return 10, 0, -1 end



for i in range(f()) do

  print(i)

end

Update #1:

Conditionals may also be moved out of the functions: --DavidManura

function range(from, to, step)

  step = step or 1

  local f =

    step > 0 and

      function(_, lastvalue)

        local nextvalue = lastvalue + step

        if nextvalue <= to then return nextvalue end

      end or

    step < 0 and

      function(_, lastvalue)

        local nextvalue = lastvalue + step

        if nextvalue >= to then return nextvalue end

      end or

      function(_, lastvalue) return lastvalue end

  return f, nil, from - step

end

Update #2:

Here is a version that in addition to treating step as optional and optimizing the inner conditional checks, allows 1 argument calls:

-- range(a) returns an iterator from 1 to a (step = 1)

-- range(a, b) returns an iterator from a to b (step = 1)

-- range(a, b, step) returns an iterator from a to b, counting by step.

function range(a, b, step)

  if not b then

    b = a

    a = 1

  end

  step = step or 1

  local f =

    step > 0 and

      function(_, lastvalue)

        local nextvalue = lastvalue + step

        if nextvalue <= b then return nextvalue end

      end or

    step < 0 and

      function(_, lastvalue)

        local nextvalue = lastvalue + step

        if nextvalue >= b then return nextvalue end

      end or

      function(_, lastvalue) return lastvalue end

  return f, nil, a - step

end

This allows for a more compact form for simple upwards counting:
-- Prints the range of numbers 1 to 10 inclusive.

for i in range(10) do

  print(i)

end

Update #3:

Here is my take, it supports both forward and backward iteration.
The other BIG thing is that iterators (imo) _should_ return at least 2 values on each successful iteration.

Consider this:



       -- Some functions accept the returned values of generators (like ipairs())

       -- and have a predefined for loop in their body like so:

       some_function = (...) local tmp = {} for k, v in ... do tmp[k] = v end return tmp end

       some_function(ipairs({ 1, 2, 3 }) -> { 1, 2, 3 }

       some_function(range(3)) -> { 1, 2, 3 }



The actual range() definition:

       -- range(start)             returns an iterator from 1 to a (step = 1)

       -- range(start, stop)       returns an iterator from a to b (step = 1)

       -- range(start, stop, step) returns an iterator from a to b, counting by step.

       range =

           function (i, to, inc)

                if i == nil then return end -- range(--[[ no args ]]) -> return "nothing" to fail the loop in the caller



               if not to then

                   to = i 

                   i  = to == 0 and 0 or (to > 0 and 1 or -1) 

               end 



               -- we don't have to do the to == 0 check

               -- 0 -> 0 with any inc would never iterate

               inc = inc or (i < to and 1 or -1) 



               -- step back (once) before we start

               i = i - inc 



               return function () if i == to then return nil end i = i + inc return i, i end 

           end 

       

Why

I had needed a range() because I wanted to do:

        to_table(range(10)) -- would create { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }

Sure you could type a literal { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 } to get the table you want, but that's not the point, it's about what having a generator like range() can let you do. It's for places where you need to provide an iterator because you can't use a numeric-for. (functional vs imperative programming?)

Elsewhere I use it to do things like this:

        -- print the sum of every cleanly-divisible-by-3 number from 1 to 100

        print(setmetatable(to_table(range(100)), { __index == 'special_table' }):remove_if(function (x) return x % 3 ~= 0 end):reduce(function (x, y) return x + y --[[ sum ]] end, 0 --[[ initial y ]]))

There are, of course, more direct ways to write this -- but let your imagination flow for a better example. :-)

Example calls:

        for i in range( 10) print(i) end -- iterate 1 to 10, increment by 1

        for i in range(-10) print(i) end -- iterate -1 to -10, decrement by 1

        for i in range(7, -2) print(i) end -- iterate 7 to -2, decrement by 1

        for i in range(3, 27, 3) print(i) end -- iterate 3 to 27, increment by 3

        for i in range(0, 1, -1) print(i) end -- iterate 0 to 1, decrementing by 1 (loop forever downward)

        for i in range() print(i) end -- error() because the call to the "returned" iterator is a nil value

-- SleepyCoder


RecentChanges · preferences
edit · history
Last edited August 5, 2012 8:55 am GMT (diff)