接触过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包的文档,迭代器的工作虽然也可以通过基础数据对象来完成,但是其简洁性、内存有好、容易设置循环中的判断条件,给以给数据处理过程带来很大便利。
github.com/ramhiser/it… github.com/cran/iterat…
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']
迭代器和生成器是函数型编程的重要技巧,这些技巧用在代码中,不但能够减少内存使用,还能够提高代码可读性。
在线课程请点击文末原文链接:
Hellobi Live | 9月12日 R语言可视化在商务场景中的应用
往期案例数据请移步本人GitHub:
github.com/ljtyduyu/Da…
来源: https://juejin.im/entry/5a1e2e5651882509e5436e1a