其是这是官方手册举例,例举了如何从原有列表派生新列表,且原列表里的每个元素都需要在新列表中生成多个对应元素的开发场景中,mapcan函数用起来格外顺手,用它来实现列表展平就是典型的应用场景。
基础应用:单层列表展开
procedure(flatten(numberList)
foreach(mapcan element numberList
element
)
)我们可以通过实际的调用示例,直观看看这个基础展平函数的执行效果:
x = '((1 2 3) (4) (5 6 7 8) () (9))
flatten(x) => (1 2 3 4 5 6 7 8 9 )
x => '((1 2 3 4 5 6 7 8 9 ) (4 5 6 7 8 9 ) (5 6 7 8 9) nil (9) )这个基础的flatten函数,核心作用就是把入参中的所有子列表直接拼接成一个单层列表。但这里有个特别需要注意的点:mapcan底层依托nconc函数实现,而nconc是典型的破坏性修改函数,会直接修改原列表的内容,这就导致调用完flatten后,原变量x的原始数据被彻底破坏,不再保留有效的初始信息。
二次升级
如果实际开发中需要保留原列表的原始值,不让x被破坏性修改,解决思路其实很简单——在foreach的遍历逻辑内部,为每个子列表做一次复制,生成一个全新的子列表来参与后续拼接,操作新列表就不会对原列表造成任何影响,实现无破坏性修改的效果。
procedure(flatten(numberList)
foreach(mapcan element numberList
if(listp(element) then
copy(element);;then
);if
);foreach
);procedure改造后的函数调用效果如下,能清晰看到展平功能正常实现的同时,原变量x的原始数据也被完整保留了:
x = '((1 2 3) (4) (5 6 7 8) () (9))
flatten(x) => (1 2 3 4 5 6 7 8 9 )
x => '((1 2 3) (4) (5 6 7 8) () (9))终级方案:实现多层嵌套列表的展平
上面改造后的函数,仅能处理单层嵌套的列表,遇到多层嵌套的复杂列表,就无法实现完全的展平了。这时候我们结合类型谓词做元素类型判断,再通过递归调用的方式对原函数做进一步改造,就能让它支持任意多层嵌套列表的深度展平了。
procedure(flatten(numberList)
foreach(mapcan element numberList
if(listp(element)
flatten(copy(element))
ncons(element)
);if
);foreach
);procedure用一个多层嵌套的复杂列表做测试,能看到函数完美实现了深度展平,同时依旧不会破坏原列表的原始结构,原变量x的内容保持不变:
x = '((1) ((2 (3) 4 ((5)) () 6) ((7 8 ()))) 9)
flatten(x)
x => '((1) ((2 (3) 4 ((5)) nil 6 ) ((7 8 nil)) ) 9 )