列表推导式

lua-users home
wiki

列表推导式 [1] 提供了简洁的语法,用于以数学集合生成器的符号构建列表。许多编程语言(例如 Haskell 和 Python)都内置支持列表推导式。Lua 没有;但是,有一些方法可以在 Lua 中实现它。

使用代码生成的纯 Lua 列表推导式

与其他一些方法不同,以下方法将列表推导式作为库在纯 Lua 中实现(无需修补或标记过滤器)。它使用动态代码生成(loadstring)和缓存的技巧,类似于 ShortAnonymousFunctions

此库已合并到 [Penlight] 中。

示例

local comp = require 'comprehension' . new()
comp 'x^2 for x' {2,3} --> {2^2,3^2}
comp 'x^2 for _,x in ipairs(_1)' {2,3} --> {2^2,3^2}
comp 'x^2 for x=_1,_2' (2,3) --> {2^2,3^2}

comp 'sum(x^2 for x)' {2,3} --> 2^2+3^2
comp 'max(x*y for x for y if x<4 if y<6)' ({2,3,4}, {4,5,6}) --> 3*5
comp 'table(v,k for k,v in pairs(_1))' {[3]=5, [5]=7} --> {[5]=3, [7]=5}

示例/测试

-- comprehension_test.lua
-- test of comprehension.lua

-- Utility function for the test suite.
-- Returns true/false whether arrays a and b
-- are equal.  This does a deep by-value comparison (supports nested arrays).
local function eqarray(a, b)
  if #a ~= #b then return false end
  for i=1,#a do
    if type(a[i]) == 'table' and type(b[i]) == 'table' then
      if not eqarray(a[i], b[i]) then return false end
    else
      if a[i] ~= b[i] then return false end
    end
  end
  return true
end

local comp = require 'comprehension' . new()

-- test of list building
assert(eqarray(comp 'x for x' {}, {}))
assert(eqarray(comp 'x for x' {2,3}, {2,3}))
assert(eqarray(comp 'x^2 for x' {2,3}, {2^2,3^2}))
assert(eqarray(comp 'x for x if x % 2 == 0' {4,5,6,7}, {4,6}))
assert(eqarray(comp '{x,y} for x for y if x>2 if y>4' ({2,3},{4,5}), {{3,5}}))

-- test of table building
local t = comp 'table(x,x+1 for x)' {3,4}
assert(t[3] == 3+1 and t[4] == 4+1)
local t = comp 'table(x,x+y for x for y)' ({3,4}, {2})
assert(t[3] == 3+2 and t[4] == 4+2)
local t = comp 'table(v,k for k,v in pairs(_1))' {[3]=5, [5]=7}
assert(t[5] == 3 and t[7] == 5)

-- test of sum
assert(comp 'sum(x for x)' {} == 0)
assert(comp 'sum(x for x)' {2,3} == 2+3)
assert(comp 'sum(x^2 for x)' {2,3} == 2^2+3^2)
assert(comp 'sum(x*y for x for y)' ({2,3}, {4,5}) == 2*4+2*5+3*4+3*5)
assert(comp 'sum(x^2 for x if x % 2 == 0)' {4,5,6,7} == 4^2+6^2)
assert(comp 'sum(x*y for x for y if x>2 if y>4)' ({2,3}, {4,5}) == 3*5)

-- test of min/max
assert(comp 'min(x for x)' {3,5,2,4} == 2)
assert(comp 'max(x for x)' {3,5,2,4} == 5)

-- test of placeholder parameters --
assert(comp 'sum(x^_1 + _3 for x if x >= _4)' (2, nil, 3, 4, {3,4,5})
       == 4^2+3 + 5^2+3)

-- test of for =
assert(comp 'sum(x^2 for x=2,3)' () == 2^2+3^2)
assert(comp 'sum(x^2 for x=2,6,1+1)' () == 2^2+4^2+6^2)
assert(comp 'sum(x*y*z for x=1,2 for y=3,3 for z)' {5,6} ==
  1*3*5 + 2*3*5 + 1*3*6 + 2*3*6)
assert(comp 'sum(x*y*z for z for x=1,2 for y=3,3)' {5,6} ==
  1*3*5 + 2*3*5 + 1*3*6 + 2*3*6)

-- test of for in
assert(comp 'sum(i*v for i,v in ipairs(_1))' {2,3} == 1*2+2*3)
assert(comp 'sum(i*v for i,v in _1,_2,_3)' (ipairs{2,3}) == 1*2+2*3)

-- test of difficult syntax
assert(eqarray(comp '" x for x " for x' {2}, {' x for x '}))
assert(eqarray(comp 'x --[=[for x\n\n]=] for x' {2}, {2}))
assert(eqarray(comp '(function() for i = 1,1 do return x*2 end end)() for x'
               {2}, {4}))
assert(comp 'sum(("_5" and x)^_1 --[[_6]] for x)' (2, {4,5}) == 4^2 + 5^2)

-- error checking
assert(({pcall(function() comp 'x for __result' end)})[2]
       :find'not contain __ prefix')

-- environment.
-- Note: generated functions are set to the environment of the 'new' call.
assert(5 == (function()
  local env = {d = 5}
  setfenv(1, env)
  local comp = comp.new()
  return comp 'sum(d for x)' {1}
end)());

print 'DONE'

运行时行为

为了说明运行时特性,请考虑以下代码

comp 'sum(x^2 for x if x % 2 == 0)'

这会生成以下 Lua 函数的代码

local __in1 = ...
local __result = (  0  )
for __idx1 = 1, #__in1 do
  local x = __in1[__idx1]
  if x % 2 == 0 then
    __result = __result + ( __x^2 )
  end
end
return __result

请注意,没有构建中间列表。代码有效地避免了内存分配(除了函数本身的分配,但这只在第一次调用时由于缓存/记忆化而完成)。此外,没有引用全局变量。

来源

[github] 下载。

注意:实现使用模块 LuaBalanced

--DavidManura

状态

此模块是新的,可能仍然存在一些错误。

可能的扩展

一个简单的扩展是提供更数学(或更像 Haskell)的语法

assert(comp 'sum { x^2 | x <- ?, x % 2 == 0 }' {2,3,4} == 2^2+4^2)

正如 Greg Fitzgerald 所建议的那样,一个引人注目的扩展是实现 SPJ 和 Wadler 提出的广义列表推导式 [2]。这为将此提升到下一个级别提供了一些明确的方向,而 Microsoft LINQ 中的相关工作 [3] 展示了这在实践中可能是什么样子。

列表推导式的“zip”扩展,使用论文中类似 Haskell 的符号

[ (x,y,z,w) | (x <- xs | y <- ys), (z <- zs | w <- ws) ] ,

只需要进行很小的更改。生成该的相应 Lua 函数如下

local __xs, __ys, __zs, __ws = ...
local __ret = {}   -- i.e. $list_init
for __i=1,__math_min(#__xs, #__ys) do
  local x, y = __xs[__i], __ys[__i]
  for __j=1,__math_min(#__zs, #__ws) do
    local z, w = __zs[__j], __ws[__j]
    __ret[#__ret+1] = {x,y,z,w}   -- i.e. $list_accum(__ret, x, y, z, w)
  end
end
return ret

(这里的“$”符号是用于扩展源代码的编译时宏的简写。)

支持排序或分组,例如,再次使用论文中的符号

[ the x.name, sum x.deposit | x <- transactions, group by x.name ] ,

可以通过生成类似这样的函数来实现

local __transactions = ...
local __groups1 = {}
local __groups2 = {}
for __i = 1, #__transactions do
  local x = __transactions[__i]
  local __key = ( x.name )  -- i.e. $group_by_key
  __groups1[__key] = ( x.name )
         -- i.e. $accum_the(__groups1[__key], $val1)
  __groups2[__key] = (__groups2[__key] or 0) + ( x.deposit )
         -- i.e. $accum_sum(__groups2[__key], $val2)
end
local __result = {}   -- i.e. $list_init
for __key in __pairs(__groups1) do
  __result[__result+1] = {__groups1[__key], __groups2[__key]}
         -- i.e. $accum_list(__result, __v)
end
return __result

经过一些研究,将它完全实现似乎相当可行,尽管这将是对该模块的重大扩展(有人愿意尝试吗?)。

更改

2009-12-01 - 修复 __call 方便操作符未缓存(由 Joshua Jensen 在邮件列表中指出)

MetaLua

列表推导也已在 MetaLua(clist)中实现。

LuaMacros

列表推导也已在 LuaMacros 中实现。

MoonScript

Moonscript 有列表推导 - http://moonscript.org/reference/#comprehensions 。Moonscript 会编译成 Lua 代码。

另请参阅


最近更改 · 偏好设置
编辑 · 历史记录
最后编辑于 2012 年 4 月 7 日下午 9:05 GMT (差异)