摘自《Python Cookbook》 4.6

  任务

  序列中的子序列可能是序列,子序列的子项仍有可能是序列,以此类推,则序列嵌套可以达到任意的深度。需要循环遍历一个序列,将其所有的子序列展开成一个单一的,只具有基本子序列的序列。(一个基本子项或者原子,可以是任何非序列的对象-或者说叶子,假如你认为序列是一棵树)

  解决方案

  我们需要能够判断哪些我们正在处理的子项是需要被展开的,那些是原子。为了获得通用性,我们使用了一个断定来作为参数,由它来判断子项是否可以展开。(断定[predicate]是一个函数,每当我们处理一个元素时就将其运用于该元素并返回一个布尔值;在这里,如果元素是一个需要展开的子序列就返沪True,否则返回False。)我们假定每一个列表或者原组都是需要被展开的,而其他类型不是。那么简单的解决方法就是提供一个递归的生成器

  

 def list_or_update(x):
return isinstance(x, (list, tuple)) def flatten(sequence, to_expand=list_or_update):
for item in sequence:
if to_expand(item):
for subitem in flatten(item, to_expand):
yield subitem
else:
yield item

  讨论

  展开一个嵌套的序列,或者等价地,按照顺序“遍历”一棵树的所有叶子,是在各种运用中很常见的任务。如果有一个嵌套的结构,元素都被组织成序列或者子序列,而且,基于某些理由,你并不关心结构本省,需要的只是一个接一个的处理所有的元素。举个例子:

 l = [['a', 'b'], 'c', ['d', ['e', ['f'], 'g']], 'h']

 for i in flatten(l):
print i,

  这个任务唯一的问题是,怎样在尽量通用的尺度下,判断什么是需要展开的,什么是需要被当作原子的,这其实没看上其那么简单。所以,我绕开直接的判断,把这个工作交给一个可调用的判定参数,调用者可以将其传递给 flatten,如果调用者满足于flatten简单的默认行为方法,即指展开原组和列表。

  在flatten所在的模块中,我们还需要提供另一个调用者可能需要用到的判定——它将展开任何非字符串(无论是普通字符串还是Unicode)的可迭代对象。字符串是可迭代,但是绝大多数运用程序还是想把他们当成原子,而不是子序列。

  至于判断对象是否可迭代,我们只需要对该对象调用内建的iter函数;若该对象不可迭代,此函数将抛出TypeError异常。为了判断对象是否是类字符串,我们则简单第检查它是否是 basestring 的实例,当obj是basestring的任何子类的实例时, isinstance(obj, basestring)的返回值将是True——这意味着任何类字符串类型。因此,这样的一个判定并不难写:

  

 def nonstring_iterable(obj):
try:
iter(obj)
except TypeError:
return False
else:
return not isinstance(obj, basestring)

  当具体的需求展开任何可迭代非字符串对象时,调用者可以调用flatten(seq, nonstring_iterable)。无疑,不把nonstring_iterable 断定作为flattern的默认选项是一个更好的选择:在简单的需求中,如我们前面展示的示例代码片段,使用nonstring_iterable会比使用list_or_tuple慢3倍以上。

  我们也可以写一个非递归版本的flattern。这种写法可以超越Python的递归层次的极限,一般不超过及千层。实现无递归遍历的要点是,采用一个明确的后进先出(LIFO)栈。在这个例子中,我们可以用迭代器的列表实现:

 def flatten(sequence, to_expand=list_or_tuple):
iterators = [iter(sequence)]
while iterators:
#循环当前的最深嵌套(最后)的迭代器
for item in iterators[-1]:
if to_expand(item):
#找到子序列,循环子序列的迭代器
iterators.append(iter(item))
break
else:
yield item
else:
#最深嵌套的迭代器耗尽,回过头来循环它的父迭代器
iterators.pop()

  其中 if 语句块的 if 子句会展开一个我们需要展开的元素——即我们需要循环遍历的子序列;所以我们该在子句中,我们将那个子序列的迭代器压入栈的末尾,在通过break打断for的执行,回到外层的while,外层while会针对我们刚刚压入的新的迭代器执行一个新的for语句。else子句则用于处理那些不需要展开的元素,它直接产生元素本身。

  如果for循环未被打断,for语句块所属的else子句将得以执行——换句话说,当for循环完全执行完毕,说明它已经遍历完当前的最新的迭代器。所以,在else子句中,我们移除了已经耗尽的嵌套最深(最近)的迭代器,之后外层的while循环继续执行,如果栈已经空了,则中止循环,如果栈中还有迭代器,则执行一个新的for循环来处理之+正好是上次执行中断的地方,本质上,迭代器的任务就是记忆迭代的状态。

  flatten的非递归实现产生的结果和前面的简单一些的递归版本的结果完全一致。如果你认为非递归实现会比递归方式快,那么你可能会失望;我采用一系列的测试用例进行观察测量,发现非递归版本比递归版本慢约10%。

  

Python展开一个嵌套的序列的更多相关文章

  1. python 展开嵌套的序列

    将一个多层嵌套的序列展开成一个单层列表 可以写一个包含yield from 语句的递归生成器来轻松解决这个问题. from collections import Iterable def flatte ...

  2. 【原创】从 列表的重复 到 用sum展开二层嵌套列表将子元素合并

      转载请注明出处:https://www.cnblogs.com/oceanicstar/p/9517159.html     ★像R语言里头有rep函数可以让向量的值重复,在python里面可以直 ...

  3. 使用python检测一个设备是否ping的通

    使用python检测一个设备是否ping的通 一,subprocess以及常用的封装函数 运行python的时候,我们都是在创建并运行一个进程.像Linux进程那样,一个进程可以fork一个子进程,并 ...

  4. python数据结构(一)------序列

    数据结构是通过某种方式(例如对元素进行编号)组织在一起的数据元素的集合:在Python中,最基本的数据结构是序列(sequence),序列中的每个元素被分配一个序列号--即元素的位置,也称为索引. p ...

  5. python基础—函数嵌套与闭包

    python基础-函数嵌套与闭包 1.名称空间与作用域 1 名称空间分为: 1 内置名称空间   内置在解释器中的名称 2 全局名称空间   顶头写的名称 3 局部名称空间 2 找一个名称的查找顺序: ...

  6. python中函数嵌套、函数作为变量以及闭包的原理

    嵌套函数: python允许创建嵌套函数.也就是说我们可以在函数里面定义函数,而且现有的作用域和变量生存周期依旧不变. 例子: #encoding=utf-8 def outer():    name ...

  7. python教程(四)·序列

    距离上次的小项目已经休息了很长一段时间,是时候来继续本系列教程了.这一节开始我们将深入python中的数据结构. 序列的概念 在python中,最基本的数据结构是序列,序列包含一个或多个元素,每个元素 ...

  8. Python决定一个变量时局部的,还是全局的,是在编译期

    Python中的变量名是在编译时就解析好的,换句话说,在编译时(也就是在交互控制台输入代码是或者import文件时),Python就已经决定一个变量应该是局部变量,还是全局变量.来看下面的例子: &g ...

  9. 孤荷凌寒自学python第三天 初识序列

    孤荷凌寒自学python第三天 初识序列 (完整学习过程屏幕记录视频地址在文末,手写笔记在文末) Python的序列非常让我着迷,之前学习的其它编程语言中没有非常特别关注过序列这种类型的对象,而pyt ...

随机推荐

  1. hdoj 2085 核反应堆【水】

    核反应堆 Time Limit: 1000/1000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others)Total Submis ...

  2. 一些正则在js使用方法

    输入框直接正则判断 <input type="password" name="pwd" placeholder="密码只能以数字\英文\@\.& ...

  3. Git git rebase 使用

      原文:http://gitbook.liuhui998.com/4_2.html 一.基本 git rebase用于把一个分支的修改合并到当前分支. 假设你现在基于远程分支"origin ...

  4. 重启adb服务

    通过命令提示符的方式来重启ADB服务的步骤如下: 1.输入adb kill-server并按下Enter键. 2.输入adb start-server并按下Enter键. 这样将会顺利地关闭ADB服务 ...

  5. yii2 and short_open_tag

    在看yii2的时候, 在main文件里看到了这样一段代码 <?= Yii::$app->language ?> 而我查看了php.ini里的配置, short_open_tag=Of ...

  6. 2015 UESTC Training for Search Algorithm & String - M - Palindromic String【Manacher回文串】

    O(n)的复杂度求回文串:Manacher算法 定义一个回文值,字符串S是K重回文串,当且仅当S是回文串,且其长度为⌊N/2⌋的前缀和长度为⌊N/2⌋的后缀是K−1重回文串 现在给一个2*10^6长度 ...

  7. Codeforces 526D - Om Nom and Necklace 【KMP】

    ZeptoLab Code Rush 2015 D. Om Nom and Necklace [题意] 给出一个字符串s,判断其各个前缀是否是 ABABA…ABA的形式(A和B都可以为空,且A有Q+1 ...

  8. CSS从大图中抠取小图完整教程(background-position应用) (转)

    自认为把background-position的应用讲得非常通俗易懂的教材.做个记号. 相信很多喜欢研究网页界面的童鞋都遇到过一个奇妙的现象:网页中很多图片素材被合成在一张图片上. 起初小菜模仿网站的 ...

  9. 学习日记_SSH框架web.xml配置文件篇

    1.参考一:http://www.blogjava.net/yxhxj2006/archive/2012/07/09/382632.html 2.参考二: <!-- web 容器启动spring ...

  10. Oracle存储过程及函数

    1.在Oracle中,存储过程包括三部分组成:定义部分.执行部分.和异常处理部分(即例外) eg1:输入员工编号,查询员工的姓名和薪资 create or repalce  procedure myp ...