接触过Python的小伙伴儿肯定都知道,Python中关于迭代器和可迭代对象运用的很广泛。迭代器可以以一种非常友好的方式使用在循环中,不仅节省内存,还能优化代码。
在R语言中,其实也有迭代的概念,但是需要借助第三方包的辅助。
今天要介绍的包是iterators和itertools,这两个包在最新开发的软件包工具中使用的非常频繁。迭代器作为一种特殊的容器,生成之后,只能按照顺序迭代完内部对象之后,便失效了,要想重新迭代就必须重新生成一个迭代器。
而我们在普通场景下构造的循环,一般都利用R语言内部的现有的数据结构(列表、向量、数据框等),这些数据结构是可见的迭代对象,而且迭代完一次之后,可以重复使用,这一点是迭代器与普通对象最大的区别。
library("iterators")
library("itertools")
iter函数可以创建一个迭代器对象,迭代器可以从所有R语言数据结构对象中创建,包括向量、矩阵、列表、数据框。
iter(obj, ...)
# a vector iterator
i1 <- iter(1:5)
> i1
$state
<environment: 0x0000000005df86c8>
$length
[1] 5
$checkFuncfunction (...)
TRUE
<environment: 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
<environment: 0x0000000016a84200>
$by
[1] "column"
$length
[1] 3
$checkFuncfunction (...)
TRUE
<environment: 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函数可以将一个可迭代对象(可以是列表、字典、元组、集合等)转换为一个迭代器。
<list_iterator at 0x2b8c927b1d0>
直接打印迭代器将会返回以上结果,迭代器将所有列表元素封装成一个可迭代的容器,迭代的最大次数即为转换前可迭代对象的长度。使用next()函数可以单次迭代一个迭代器,直至迭代到最大次数,迭代器失效,再次迭代将会抛出错误。
next(I)
'J'
next(I)
'E'
next(I)
'F'
next(I)
'W'
next(I)
'Y'
Traceback (most recent call last):
File "<ipython-input-24-032af8264890>", line 1, in <module>
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
<generator object <genexpr> 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)
<generator object odd at 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