作者Wush978 (拒看低质媒体)
看板R_Language
标题[心得] itertools简介
时间Fri Mar 10 17:09:16 2017
[关键字]: R, loop
网志版:
http://wush.ghost.io/itertools-intro/
---
# itertools 简介
Wush Wu
March 10, 2017
最近在ptt R_Language版上看到许多跟回圈有关的文章,所以一时兴起想跟大家分享写回
圈或apply等函数好用的套件:itertools
```r
library(itertools)
```
```
## Loading required package: iterators
```
讲itertools之前,要先介绍iterator的概念:这是把回圈的功能更精链出来的概念。
我们先看一个回圈的范例:
```r
for(i in 1:3) {
print(i)
}
```
```
## [1] 1
## [1] 2
## [1] 3
```
这段回圈的灵魂,在於变数`i`。透过`i in 1:3`,R 就知道`i`的值有以下规则:
- 从`1`开始
- 每次递增1
- 到`3`结束
更一般来说,R 的回圈是透过一个Vector物件,告诉R要如何执行回圈。举例来说,`i
in x`即代表:
- 从`x[1]`开始
- `x[i]`结束之後执行`x[i+1]`
- 到`x[length(x)]`结束
但是我们可以再更精链这样的概念。而许多工具中,就会设计`iterator`这样的物件,并
且让他具备以下两种功能
- 有没有下一个值
- 取出下一个值,并且往前推进
有这两个概念即可建立一个回圈。
举例来说,以下两个回圈是等价的:
```r
for(i in 1:3) {
print(i)
}
```
```
## [1] 1
## [1] 2
## [1] 3
```
```r
i <- 0
while(i < 3) {
i <- i + 1
print(i)
}
```
```
## [1] 1
## [1] 2
## [1] 3
```
这里的`i < 4`代表`有没有下一个值`的逻辑判断,而`i <- i + 1`则代表`取出下一个值
,并且往前推进`。
itertools套件会建立符合上述概念的物件,并称之为`iterator`。
透过iterator之间的运算,我们可以轻松写出复杂的回圈结构
## 范例一:双层回圈
有时候当我们需要走遍整个矩阵时,我们可能会写出类似以下程式码的回圈结构:
```r
for(i in 1:3) {
for(j in 1:3) {
print(paste(i, j))
}
}
```
```
## [1] "1 1"
## [1] "1 2"
## [1] "1 3"
## [1] "2 1"
## [1] "2 2"
## [1] "2 3"
## [1] "3 1"
## [1] "3 2"
## [1] "3 3"
```
运用itertools时,我们可以透过`product`来产生相同的效果:
```r
it <- ihasNext(product(i = 1:3, j = 1:3))
while(hasNext(it)) {
x <- nextElem(it)
print(paste(x$i, x$j))
}
```
```
## [1] "1 1"
## [1] "1 2"
## [1] "1 3"
## [1] "2 1"
## [1] "2 2"
## [1] "2 3"
## [1] "3 1"
## [1] "3 2"
## [1] "3 3"
```
itertools产生的iterator不能直接在for之中使用,必须要搭配`ihasNext`、`hasNext`
与`nextElem`来做出上述概念的程式码。
但是我们可以直接拿iterator与`lapply`搭配:
```r
result <- lapply(product(i = 1:3, j = 1:3), function(x) {
print(paste(x$i, x$j))
})
```
```
## [1] "1 1"
## [1] "1 2"
## [1] "1 3"
## [1] "2 1"
## [1] "2 2"
## [1] "2 3"
## [1] "3 1"
## [1] "3 2"
## [1] "3 3"
```
## 范例二: 合并回圈
有时候我们有两个vector要一起做回圈,这时候只能透过对座标做回圈来达成。举例来
说:
```r
x <- 1:3
y <- 4:6
for(i in seq_along(x)) {
print(paste(x[i], y[i]))
}
```
```
## [1] "1 4"
## [1] "2 5"
## [1] "3 6"
```
但是这种程式码在x, y 长度不同时不一定会出错。
运用itertools时,我们可以透过`izip`来产生相同的效果:
```r
it <- ihasNext(izip(x = 1:3, y = 4:6))
while(hasNext(it)) {
x <- nextElem(it)
print(paste(x$x, x$y))
}
```
```
## [1] "1 4"
## [1] "2 5"
## [1] "3 6"
```
## 范例三: data.frame
在使用data.frame时,我们常常想要把data.frame的row走一遍:
```r
df <- iris[1:3,]
for(i in seq_len(nrow(df))) {
x <- df[i,]
print(paste(x$Sepal.Length, x$Sepal.Width, x$Petal.Length, x$Petal.Width,
x$Species))
}
```
```
## [1] "5.1 3.5 1.4 0.2 setosa"
## [1] "4.9 3 1.4 0.2 setosa"
## [1] "4.7 3.2 1.3 0.2 setosa"
```
而itertools可以直接指定走的方向:
```r
it <- ihasNext(iter(iris[1:3,], by = "row"))
while(hasNext(it)) {
x <- nextElem(it)
print(paste(x$Sepal.Length, x$Sepal.Width, x$Petal.Length, x$Petal.Width,
x$Species))
}
```
```
## [1] "5.1 3.5 1.4 0.2 setosa"
## [1] "4.9 3 1.4 0.2 setosa"
## [1] "4.7 3.2 1.3 0.2 setosa"
```
## 范例四: 批次回圈
itertools也可以建立批次处理的回圈:
```r
it <- ihasNext(ichunk(1:10, 3))
while (hasNext(it)) {
print(unlist(nextElem(it)))
}
```
```
## [1] 1 2 3
## [1] 4 5 6
## [1] 7 8 9
## [1] 10
```
## 范例四: 截断回圈
itertools也可以控制让回圈提早中止:
```r
mkfinished <- function(time) {
starttime <- proc.time()[3]
function() proc.time()[3] > starttime + time
}
f <- mkfinished(1) # 这是个函数,当时间比这个瞬间晚1秒时,f就会回传FALSE, 回圈
会中止
# 看看1秒内,回圈可以跑多少
length(lapply(ibreak(iter(1:1000000), f), function(x) {
# do something
}))
```
```
## [1] 25499
```
为了更简单的使用时间限制的功能,itertools提供了`timeout`
```r
it <- ihasNext(timeout(iter(1:1000000), 1))
count <- 0
while(hasNext(it)) {
x <- nextElem(it)
count <- count + 1
}
count
```
```
## [1] 17932
```
也可以给定长度,截断回圈
```r
length(lapply(ilimit(iter(1:1000000), 100), function(x) {
# do something
}))
```
```
## [1] 100
```
## 范例五: 重复回圈
我们也可以重复一个iterator若干次,甚至是无限次
```r
it <- ihasNext(recycle(iter(1:3), 2))
while(hasNext(it)) {
x <- nextElem(it)
print(x)
}
```
```
## [1] 1
## [1] 2
## [1] 3
## [1] 1
## [1] 2
## [1] 3
```
## 总结
以上我们展示了一些itertools提供的部份功能。它还有其他有趣的功能可以探索。
总之,当R友们在写回圈时,如果遇到比较复杂的回圈情境,建议可以看看itertools这个
套件有没有提供帮助。
--
※ 发信站: 批踢踢实业坊(ptt.cc), 来自: 1.161.227.161
※ 文章网址: https://webptt.com/cn.aspx?n=bbs/R_Language/M.1489136959.A.CD4.html
※ 编辑: Wush978 (1.161.227.161), 03/10/2017 17:34:51
※ 编辑: Wush978 (1.161.227.161), 03/10/2017 18:37:53
1F:推 Neisseria: 蛮有 func lang 的 fu,走 itertools 效能会比较好吗? 03/10 18:46
2F:推 obarisk: 把所有东西都做成map, reduce不是很好吗(误) 03/10 18:58
3F:推 celestialgod: 推 03/10 19:08
4F:推 locka: 感谢介绍!让程式写起来更简洁,不过同样好奇效能XD 03/10 19:48
5F:→ Wush978: 都写for loop了还要在乎效能嘛?(误) 03/10 20:04
6F:推 cywhale: 推 很有意思,多谢分享~~ 03/10 20:16
7F:推 criky: 推一个~也好奇效能 XD 03/11 09:58
在网志上做了测试并给了comment:
http://wush.ghost.io/itertools-performance/
这里只贴重点不贴原文了(每次贴每次当)
```r
f1 <- function() {
lapply(1:100, function(i) {
lapply(1:100, function(j) {
})
})
NULL
}
f2 <- function() {
lapply(product(i = 1:100, j = 1:100), function(x) { })
NULL
}
microbenchmark(f1(), f2(), times = 10)
```
```
## Unit: milliseconds
## expr min lq mean median uq max
## f1() 4.657429 5.329925 6.092346 5.896559 6.685231 8.482395
## f2() 466.092096 485.819743 504.164424 500.838942 522.266778 538.185611
## neval
## 10
## 10
```
在我的电脑上,差不多是5 vs 500 milliseconds 的差异,也就是100倍。 看起来很多,
可是在实务上呢?
如果# do something每次花1 milli seconds做计算,那整体的时间差异也是: 10000 +
5 v.s. 10000 + 500, 而在10000面前,你不太会注意到那100倍的差距。
就我自己用itertools的经验时,通常是在写一些不是效能很重要的程式码。ps. 效能重
要的程式码我会用C++写。 有时候,当写一个只会跑若干次的程式时,为了省那不到一秒
的时间,而去写更难写更复杂的程式码,反而花更多时间,并且得不偿失阿。 所以我在
看到一些R友问效能的时候,心里其实是感到满讶异的: 大家是不是走火入魔了? 并不是
只有跑得快才有价值,有的时候能把程式码弄的更简单,也是很有价值的。而这个套件的
价值,偏向後者。
※ 编辑: Wush978 (1.163.178.151), 03/11/2017 16:19:43
8F:推 a78998042a: 推 03/14 00:29