#!/usr/bin/luaIn the preceding code, function iter() is the function within a function, and iter() can see surrounding function maker()'s local variable, n. Now here's the thing: Because n is a local variable of maker(), every time maker() is run, a completely new and separate copy of n is instantiated and initialized (to 0). Also, every time maker() is run, it returns a new copy of iter(), which of course sees the newly initialized (to 0) n from maker(). iter() always increments before returning the new n so the originally instantiated 0 value is incremented to 1, so n returns 1 the first time it's called. So maker() serves as sort of a factory (which is why it's called "maker") for copies of iter(). Since iter() increments the value of its copy of n every time it runs, it keeps returning higher values.
function maker()
local n = 0
function iter()
n = n + 1
return n
end
return iter
end
iter_a = maker() -- Make an iterator
iter_b = maker() -- Make a different iterator
print(iter_a()) -- Should print 1
print(iter_a()) -- Should print 2
print(iter_b()) -- Should print 1
print(iter_a()) -- Should print 3
print(iter_b()) -- Should print 2
Naming the function you return |
|
Returning an anonymous function |
function maker() |
function maker() |
#!/usr/bin/luaThe preceding code yields the following output:
function positive_integers(max)
local n = 0
return function()
n = n + 1
if n > max then
return nil
else
return n
end
end
end
for v in positive_integers(3) do
print(v)
end
print("================")
for v in positive_integers(5) do
print(v)
end
slitt@mydesk:/d/websites/tjunct/codecorn/lua$ ./test.luaCool! You tell it to iterate 3 times, it iterates 3. Tell it 5, it iterates 5. Let's see what happened:
1
2
3
================
1
2
3
4
5
slitt@mydesk:/d/websites/tjunct/codecorn/lua$
function positive_integers(max) |
|
The code on the left is pretty
similar to the code from the Hello World article except you're passing
the maximum value in as the argument to the maker, positive_integers(). In the returned function you not only increment n, but also if it's over the maximum you return nil. Generic for loops stop the first time their iterator returns nil.
That's why they don't loop infinitely. So any time you make an iterator
meant to be used in a generic for loop, be sure that iterator returns nil after the last good data. |
iterator_fcn = iter_maker() -- Assign new iterator function to iterator_fcnThe for loop operates on the iterator function. Not on what the iterator function returns, and not on the maker function, but on the iterator function. Notice that on the for line, iterator_fcn had no parentheses.
for k,v in iterator_fcn do
print(string.format("k=%s, v=%s", tostring(k), tostring(v)))
end
for k,v in iter_maker() doLook at the preceding code until you understand why it's the same as the code before it. The for line requires an iterator function, which is exactly what the maker function returns.
print(string.format("k=%s, v=%s", tostring(k), tostring(v)))
end
#!/usr/bin/luaThe preceding code produces the following output:
local sf = string.format -- alias to shorten print lines
function fibonaccis(max)
local b4 = -1
local now = 1
local key = -1
return function()
value = b4 + now
if value > max then
return nil, nil
else
b4 = now
now = value
key = key + 1
return key, value
end
end
end
for k,v in fibonaccis(100) do
print(sf("k=%2d, v=%2d", k, v))
end
slitt@mydesk:/d/websites/tjunct/codecorn/lua$ ./test.luaRemember that k and v stand for "key" and "value", and they not have to be declared, and they're local to the loop. If you need either outside the loop, and it's entirely possible you will, you need to assign the one you need (or both) to a variable(s) with scope outside the loop.
k= 0, v= 0
k= 1, v= 1
k= 2, v= 1
k= 3, v= 2
k= 4, v= 3
k= 5, v= 5
k= 6, v= 8
k= 7, v=13
k= 8, v=21
k= 9, v=34
k=10, v=55
k=11, v=89
slitt@mydesk:/d/websites/tjunct/codecorn/lua$
#!/usr/bin/luaThe preceding code predictably produces the following output:
local sf = string.format -- alias to shorten print lines
function fibonaccis(max)
local maxkey_flag = false
local b4 = -1
local now = 1
local key = -1
if max < 0 then
maxkey_flag = true
max = max * -1
end
return function()
key = key + 1
value = b4 + now
if maxkey_flag and key > max or
not maxkey_flag and value > max then
return nil, nil
else
b4 = now
now = value
return key, value
end
end
end
for k,v in fibonaccis(13) do
print(sf("k=%2d, v=%2d", k, v))
end
print("============")
for k,v in fibonaccis(-8) do
print(sf("k=%2d, v=%2d", k, v))
end
slitt@mydesk:/d/websites/tjunct/codecorn/lua$ ./test.luaWhen max was positive, it terminated based on value. When max was negative it terminated based on key. Study the preceding until you understand it. It's really not much different from the first Fibonacci iterator.
k= 0, v= 0
k= 1, v= 1
k= 2, v= 1
k= 3, v= 2
k= 4, v= 3
k= 5, v= 5
k= 6, v= 8
k= 7, v=13
============
k= 0, v= 0
k= 1, v= 1
k= 2, v= 1
k= 3, v= 2
k= 4, v= 3
k= 5, v= 5
k= 6, v= 8
k= 7, v=13
k= 8, v=21
slitt@mydesk:/d/websites/tjunct/codecorn/lua$
-- relevent_lines.lua Copyright (C) 2011 by Steve Litt, all rights reserved.OK, let's discuss the preceding code. It's a Lua module. You know that by the fact that the first non-comment line that looks like this:
--
-- Permission is hereby granted, free of charge, to any person obtaining a copy
-- of this software and associated documentation files (the "Software"), to deal
-- in the Software without restriction, including without limitation the rights
-- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-- copies of the Software, and to permit persons to whom the Software is
-- furnished to do so, subject to the following conditions:
--
-- The above copyright notice and this permission notice shall be included in
-- all copies or substantial portions of the Software.
--
-- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
-- THE SOFTWARE.`
-- Version 0.0.1, pre-alpha
-- relevant_lines() is an iterator maker that takes a table as its one and only
-- argument. At minimum this table must have a key called "file" whose value is
-- either the filename of an input file or an open-for-read handle. Other possible
-- elements to put in this table include:
-- this_line_number: One less than the first key to be delivered, defaults to 0
-- is_relevant: A callback function determining whether to bestow or skip this line
-- is_relevent defaults to "pass back all non-blank lines"
-- tweak: A callback to do other operations to relevant lines
--
-- Because of the table nature of the argument, you can put pretty much
-- everything but the kitchen sink, and as long as either is_relevant() or
-- tweak() calls it, it will form part of the algorithm.
module(..., package.seeall);
function relevant_lines(tab)
--print(type(tab.file))
--os.exit(1)
local handle
tab.this_line_text = nil
tab.prev_line_text = nil
tab.this_line_number = tab.this_line_number or 0
tab.prev_line_number = -1
--### GET FILE HANDLE UP AND RUNNING
if tab == nil then
io.stderr:write("ERROR: Function relevant_lines() must have a single argument, a table.\n")
io.stderr:write("Aborting...\n\n")
os.exit(1)
end
if type(tab.file) == "nil" then
io.stderr:write("ERROR: Table argument to relevant_lines() must have an element called file.\n")
io.stderr:write("Aborting...\n\n")
os.exit(1)
elseif type(tab.file) == "userdata" then
handle = tab.file
elseif type(tab.file) == "string" then
handle = assert(io.open(tab.file, "r"))
else
io.stderr:write("ERROR: Function relevant_lines(): tab.file has wierd type.\n")
io.stderr:write("Aborting...\n\n")
os.exit(1)
end
--### IF YOU GOT HERE, YOU OPENED THE FILE FOR INPUT
--### DEFAULT THE CALLBACK IF NECESSARY. DEFAULT TO SKIP BLANK LINES
if not tab.is_relevant then
tab.is_relevant = function()
return string.match(tab.this_line_text, "%S") -- skip blanks
end
end
--### DEFINE THE ITERATOR TO RETURN
return function()
-- Read line and increment line count
tab.this_line_text = handle:read("*line")
tab.this_line_number = tab.this_line_number + 1
-- Blow off any nonrelevant lines
while tab.this_line_text and not tab.is_relevant() do
tab.this_line_text = handle:read("*line")
tab.this_line_number = tab.this_line_number + 1
end
-- Return nil if eof, and close handle if made from string
if tab.this_line_text == nil then
if type(tab.file) == "string" then
io.close(handle)
end
return nil, nil
else -- Run tweak procedure and then return the line number and text
if tab.tweak then tab.tweak() end;
return tab.this_line_number, tab.this_line_text
end
end
end
module(..., package.seeall);I'm not going to explain the preceding line other than to say that line is how you make a Lua file into a module that can be incorporated by the require() function.
oneAs you can see, several lines are blank, either consisting only of a newline, or of a newline plus some spaces or tabs. Sime lines are commented out with the traditional Unix comment character #. By default relevant_lines() screens out blank lines but leaves commented lines intact.
two
# three
#four
#five
six
seven
#!/usr/bin/luaThe first line is the standard Lua shebang indicating to run the code through Lua. The second line imports relevant_lines.lua, which as you remember you specially outfitted as a module with the module() command. The third line assigns function string_format to shorter named variable sf. This makes print lines much shorter. I always do this. The fourth line assigns function relevant_lines within module relevant_lines to local variable relevant_lines so you needn't write relevant_lines.relevant_lines() everywhere. In general be careful naming a local function the same name as the module's name, because once you do that, the module is no longer accessible. That's OK here because the module contains only one function and you already assigned it to the local.
require("relevant_lines")
sf = string.format
local relevant_lines = relevant_lines.relevant_lines
tab = {file = "test.txt"}The preceding iterates through test.txt using the iterator's default is_relevant(), which passes all lines containing a nonblank character. The next few lines are slightly different:
for k, v in relevant_lines(tab) do
print(sf("k=%d, v=%s", tab.this_line_number, tab.this_line_text))
end
tab.this_line_number = 0The first line sets the table's line counter back to zero, because the last iteration had iterated the line counter. The second line defines an is_relevant() function that returns all lines, even blank ones. The third line prints a line to separate this iteration from the last one, and then the generic for loop prints all the lines.
tab.is_relevant = function() return true end
print("=====================")
for k, v in relevant_lines(tab) do
print(sf("k=%d, v=%s", tab.this_line_number, tab.this_line_text))
end
tab.this_line_number = 0In the preceding code, once again the first line resets the line counter. The next four lines define a more complex is_relevant() function, specifically one that not only contains at least one printable character, but also does not start with spaces and then a pound sign. In other words, it passes everything but blank lines and comment lines (in Bash or Perl). The next line defines a tweak() function that upper cases the line, the next one draws a line to separate from previous iterations, and then the generic for loop returns the few nonblank, non-comment lines, after turning them upper case.
tab.is_relevant = function()
return string.match(tab.this_line_text, "%S") and
not string.match(tab.this_line_text, "^%s*#")
end
tab.tweak = function() tab.this_line_text = string.upper(tab.this_line_text) end
print("=====================")
for k, v in relevant_lines(tab) do
print(sf("k=%d, v=%s", tab.this_line_number, tab.this_line_text))
end
tab.this_line_number = 1000The preceding code first set the beginning line number to 1000 so the first returned line would be number 1001. Then it set the file to stdin, so it works against text you type in or text that gets piped in. The is_relevant() and tweak() functions remain unchanged, so once again only nonblank non-Bash-comment lines come into the loop, and once again those lines are upper case.
tab.file = io.stdin
print("=====================")
for k, v in relevant_lines(tab) do
print(sf("k=%d, v=%s", tab.this_line_number, tab.this_line_text))
end
slitt@mydesk:~$ ./testrel.luaAs expected, the first loop showed only nonblanks, the second showed all lines, the third showed only nonblank, non comments and capitalized them, and the fourth took text I typed in, and for nonblank, non-comment lines it printed them, capitalized.
k=1, v=one
k=2, v=two
k=4, v=# three
k=5, v=#four
k=9, v= #five
k=10, v= six
k=12, v=seven
=====================
k=1, v=one
k=2, v=two
k=3, v=
k=4, v=# three
k=5, v=#four
k=6, v=
k=7, v=
k=8, v=
k=9, v= #five
k=10, v= six
k=11, v=
k=12, v=seven
=====================
k=1, v=ONE
k=2, v=TWO
k=10, v= SIX
k=12, v=SEVEN
=====================
Line one
k=1001, v=LINE ONE
# comment line two
#comment line three
Line five
k=1005, v= LINE FIVE
slitt@mydesk:~$
[ Troubleshooters.com| Code Corner | Email Steve Litt ]
Copyright (C) 2011 by Steve Litt --Legal