左手用R右手Python系列之——迭代器与迭代对象

浏览: 1350

接触过Python的小伙伴儿肯定都知道,Python中关于迭代器和可迭代对象运用的很广泛。迭代器可以以一种非常友好的方式使用在循环中,不仅节省内存,还能优化代码。

在R语言中,其实也有迭代的概念,但是需要借助第三方包的辅助。

今天要介绍的包是iterators和itertools,这两个包在最新开发的软件包工具中使用的非常频繁。迭代器作为一种特殊的容器,生成之后,只能按照顺序迭代完内部对象之后,便失效了,要想重新迭代就必须重新生成一个迭代器。

而我们在普通场景下构造的循环,一般都利用R语言内部的现有的数据结构(列表、向量、数据框等),这些数据结构是可见的迭代对象,而且迭代完一次之后,可以重复使用,这一点是迭代器与普通对象最大的区别。

library("iterators")
library("itertools")

iter函数可以创建一个迭代器对象,迭代器可以从所有R语言数据结构对象中创建,包括向量、矩阵、列表、数据框。

iter(obj, ...)
# a vector iterator

i1 <- iter(1:5)
> i1
$state
0x0000000005df86c8
>

$length
[1] 5

$checkFuncfunction (...)
TRUE
0x0000000005dfb860>

$recycle
[1] FALSE

attr(,"class")
[1] "containeriter" "iter"

nextElem可以逐次迭代迭代器内的单个对象,当迭代次数用完了之后,便会抛出错误。

> nextElem(i1)
[1] 1

> nextElem(i1)
[1] 2

> nextElem(i1)
[1] 3

> nextElem(i1)
[1] 4

> nextElem(i1)
[1] 5

> nextElem(i1)
Error: StopIteration

迭代数据框:

# a data frame iterator by column
i2 <- iter(data.frame(x=1:3, y=10, z=c('a', 'b', 'c')))
$state
0x0000000016a84200
>

$by
[1] "column"

$length
[1] 3

$checkFuncfunction (...)
TRUE
0x0000000016a85f20>

$recycle
[1] FALSE

attr(,"class")
[1] "dataframeiter" "iter"
nextElem(i2)
[1] 1 2 3

> nextElem(i2)
[1] 10 10 10

> nextElem(i2)
[1] a b c
Levels: a b c

对于数据框而言,默认按列迭代,最大迭代次数为列数,迭代完为止。

i2 <- iter(data.frame(x=1:3, y=10, z=c('a', 'b', 'c')),by="row")
> nextElem(i2)
 x  y z
1 1 10 a
> nextElem(i2)
 x  y z
2 2 10 b
> nextElem(i2)
 x  y z
3 3 10 c

设置迭代依据参数by可以控制迭代方式,这里将by设为row迭代即为按行迭代。

迭代器可以通过as.list函数进行还原。

iter1<-iter(LETTERS[1:10])
as.list(iter1)
[[1]]
[1] "A"

[[2]]
[1] "B"

[[3]]
[1] "C"

[[4]]
[1] "D"

[[5]]
[1] "E"

[[6]]
[1] "F"

[[7]]
[1] "G"

[[8]]
[1] "H"

[[9]]
[1] "I"

[[10]]
[1] "J"

转换之后,迭代器便失效了,nextElem(iter1)会报错。

enumerate函数可以将列表或者向量进行键值对形式的迭代(Python中就有同名的函数,这并不奇怪,因为以上两个包中的所有函数都是参照Python中的迭代器包设计的)。

myvector<-LETTERS[1:5]
iter2 <- enumerate(myvector)
nextElem(iter2)

$index
[1] 1

$value
[1] "A"

> nextElem(iter2)
$index
[1] 2

$value
[1] "B"

> nextElem(iter2)
$index
[1] 3

$value
[1] "C"

> nextElem(iter2)
$index
[1] 4

$value
[1] "D"

> nextElem(iter2)
$index
[1] 5

$value
[1] "E"

hasNext函数可以将一个迭代器内的每一次迭代行为添加是否可迭代的标记,当迭代次数没有达到最大值时,显示可继续迭代,否则显示剩余可迭代次数为零。

it <- ihasNext(LETTERS[1:5])
while (hasNext(it))
   cat(nextElem(it),"\n")
A
B
C
D
E

使用hasNext函数作为迭代器判定条件,当迭代次数完成之后,越界的迭代就会立即停止。

除了以上函数之外,还有很多定制的迭代函数,用于迭代不同场景下的数据结构。

x <- array(1:24, c(2,3,4))
, , 1

    [,1] [,2] [,3]
[1,]    1    3    5

[2,]    2    4    6

, , 2

    [,1] [,2] [,3]
[1,]    7    9   11

[2,]    8   10   12

, , 3

    [,1] [,2] [,3]
[1,]   13   15   17

[2,]   14   16   18

, , 4

    [,1] [,2] [,3]
[1,]   19   21   23

[2,]   20   22   24

as.list(iarray(x, 1)) #迭代各维度子数据块儿的行

[[1]]
    [,1] [,2] [,3] [,4]
[1,]    1    7   13   19

[2,]    3    9   15   21

[3,]    5   11   17   23

[[2]]
    [,1] [,2] [,3] [,4]
[1,]    2    8   14   20

[2,]    4   10   16   22

[3,]    6   12   18   24

as.list(iarray(x, 2)) #迭代各维度子数据块儿的列

[[1]]
    [,1] [,2] [,3] [,4]
[1,]    1    7   13   19

[2,]    2    8   14   20

[[2]]
    [,1] [,2] [,3] [,4]
[1,]    3    9   15   21

[2,]    4   10   16   22

[[3]]
    [,1] [,2] [,3] [,4]
[1,]    5   11   17   23

[2,]    6   12   18   24

更多的迭代器函数,可以参考itertools、itertor包的文档,迭代器的工作虽然也可以通过基础数据对象来完成,但是其简洁性、内存有好、容易设置循环中的判断条件,给以给数据处理过程带来很大便利。

https://github.com/ramhiser/itertools2
https://github.com/cran/iterators

Python

之前讲解R语言中迭代器概念的时候曾说过,R语言iterator包、itertools包是参照Python中的迭代器理念设计的,所以理念和用法都差不多。

import string,random
iter1 = random.sample(string.ascii_letters[26:],5)
['J', 'E', 'F', 'W', 'Y']
I=iter(iter1)

使用iter函数可以将一个可迭代对象(可以是列表、字典、元组、集合等)转换为一个迭代器。

直接打印迭代器将会返回以上结果,迭代器将所有列表元素封装成一个可迭代的容器,迭代的最大次数即为转换前可迭代对象的长度。使用next()函数可以单次迭代一个迭代器,直至迭代到最大次数,迭代器失效,再次迭代将会抛出错误。

next(I)
'J'

next(I)
'E'

next(I)
'F'

next(I)
'W'

next(I)
'Y'

Traceback (most recent call last):

 File "", line 1, in
   next(I)

StopIteration

使用isinstance函数可以检测一个对象是否是迭代器。

from collections import Iterator, Iterable

isinstance(iter1, Iterable)
True

isinstance(iter1, Iterator)
False

isinstance(I, Iterable)
True

isinstance(I, Iterator)
True

可以看到,转换前的列表仅仅是可迭代对象,而不是迭代器,而转换后的迭代器对象,即是可迭代对象,也是迭代器。

一个迭代器可以被for循环直接访问(在R中好像不允许)。

I=iter(iter1)
for i in I:
   print(i,"\n")
J

E

F

W

Y

for循环访问迭代器,迭代至最大次数之后,迭代器失效,循环停止并自动跳出,无需设置跳出条件。

在Python中与迭代器经常一起被提起的就是生成器了(关于生成器目前在R语言中还没有看到很好的实例)。

使用各种推导式函数可以很方便的改造成生成器。

import string,random
gen = random.sample(string.ascii_letters[26:],5)
gen1 = (i.lower() for i in gen)
type(gen1)
Out[41]: generator
at 0x000002B8C9285D00>

一个典型的生成器格式如上所示。

生成器也可以直接被for循环直接访问并遍历其中对象。

for i in gen1:
   print(i,"\n")
t

p

f

n

h

而另一种生成器的产生方式是使用yield函数。

def odd(gen):
   for i in gen:
       yield i

mygen = odd(gen)
0x000002B8C92B5E08
>

迭代器和生成器可以通过list函数转换为普通的list对象,两者与内建数据对象的最大区别是,迭代器和生成的元素是不可见的,只有在使用循环访问或者使用next函数调用的时候才能输出内部元素。迭代器和生成器迭代到最大次数之后便失效了。

list(mygen)
Out[48]: ['T', 'P', 'F', 'N', 'H']
list(iter(['T', 'P', 'F', 'N', 'H']))
Out[50]:['T', 'P', 'F', 'N', 'H']

迭代器和生成器是函数型编程的重要技巧,这些技巧用在代码中,不但能够减少内存使用,还能够提高代码可读性。

在线课程请点击文末原文链接:
往期案例数据请移步本人GitHub:
https://github.com/ljtyduyu/DataWarehouse/tree/master/File

欢迎关注数据小魔方qq交流群

image.png

推荐 1
本文由 杜雨 创作,采用 知识共享署名-相同方式共享 3.0 中国大陆许可协议 进行许可。
转载、引用前需联系作者,并署名作者且注明文章出处。
本站文章版权归原作者及原出处所有 。内容为作者个人观点, 并不代表本站赞同其观点和对其真实性负责。本站是一个个人学习交流的平台,并不用于任何商业目的,如果有任何问题,请及时联系我们,我们将根据著作权人的要求,立即更正或者删除有关内容。本站拥有对此声明的最终解释权。

0 个评论

要回复文章请先登录注册