摘自《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. [IE9] GPU硬件加速

      IE9 的一个重大改进就是使用了GPU硬件加速来渲染网页. 那么GPU硬件加速到底能够带来多大的性能提升? 你可以在IE的测试案例网站(http://ie.microsoft.com/testdr ...

  2. RabbitMQ-清空队列中(一个channel或连接中)的Unacknowledged状态的消息

    清空所有:nack 时将参数delivery-tag设为0,multiple设为1. 清空小于等于某delivery-tag的所有消息:nack 时将参数delivery-tag设为正数(介于1和92 ...

  3. 代码对齐 分类: C#小技巧 2014-04-17 14:45 166人阅读 评论(0) 收藏

    开发项目时,为了是代码层次清晰.美观,常常需要调整多行,使之对齐.在网上也看到一些方法,感觉不好用,偶尔发现一个小技巧. (1)多行代码同时右移 同时选中几行,按"Tab"键,就会 ...

  4. DataGridView绑定BindingList<T>带数据排序的类

    本文章转载:http://yuyingying1986.blog.hexun.com/30905610_d.html DataGridView绑定List<T>类时候,不会自动的进行排序. ...

  5. 再探java基础——对面向对象的理解(2)

    对象.类和抽象类的区别 对象是一个具体的事物,类是对具有相同属性和行为的一组对象的抽象,对象是类的一个一个人的具体实例:抽象类是一种特殊的类,是对类的进一步抽象,抽象类不能被实例化. 类.抽象类和接口 ...

  6. [javascirpt] Regex

    To Currency function toCurrency(price){ return price.toString().replace(/(\d)(?=(\d{3})+(?!\d))/g, & ...

  7. ThinkPHP pdo连接Oracle的配置写法,提示报错

    'DB_TYPE' => 'pdo', // 数据库类型 'DB_USER' => 'user101', // username 'DB_PWD' => 'zb~!@#$%', // ...

  8. Linux磁盘管理:LVM逻辑卷基本概念及LVM的工作原理

    一.传统的磁盘管理 其实在Linux操作系统中,我们的磁盘管理机制和windows上的差不多,绝大多数都是使用MBR(Master Boot Recorder)都是通过先对一个硬盘进行分区,然后再将该 ...

  9. linux之CentOS-7.0环境搭建

    此文作为新手安装centos-7的图文教程.  一.  前言 最近,师兄要进行实验室架构搭建,需要学习docker.而docker是完全依赖于linux系统的.所以,有了这篇文章. linux有很多发 ...

  10. CSS入门学习(转)

    一.基础学习 1.何为CSS CSS是Cascading Style Sheets(层叠样式表)的简称,是一种标记语言,它不需要编译,可以直接由浏览器执行(属于浏览器解释型语 言). CSS文件也可以 ...