列表推导式

lua-users home
wiki

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

纯 Lua 代码生成实现列表推导式

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

该库已集成到 [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

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

支持 sort 或 grouped by,例如,再次使用论文中的符号

[ 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 代码。

另请参阅


RecentChanges · preferences
编辑 · 历史
最后编辑于 2012 年 4 月 7 日下午 3:05 GMT (diff)