最近在处理很多数据,R 中我是一直使用简洁好看好理解的 tidyverse
, 然后遇到一个问题,比如说我们有数据
A | B |
---|---|
1 | String1 |
2 | String2 |
希望将 A
列和B
列进行位置交换。最讨巧的自然是重命名,tidyverse
里面的重命名是不触发 copy 的,但是这里不考虑。一开始我想的就是通过额外生成两列,然后重新赋值达到效果。
# 第一个实现
## 也许有人对这个符号很疑惑,先解释一下
## 在 tidyverse 中, x %>% f(...) 大致等价于 f(x, ...)
## 为了遵循思路的流动,所以用 %>% 写容易看懂
data %>%
mutate( C = A, D = B) %>% # 使C拥有A的数据, D拥有B
mutate( A = D, B = C) %>% # 使A拥有D的数据, B拥有C
select( A, B) # 只输出A与B列的数据
哼,非常简单,但是想想又很不爽,心里总有一个疙瘩,因为希望的是通过
data %>% (function (data, x, y){...})(A, B)
这样一个匿名函数得到结果。所以立刻有了一个想法
# 第二个实现
data %>% (function (data, x, y){
tmp <- data %>% pull({{x}})
data %>% mutate({{x}} := {{y}}, {{y}} := tmp)
})(A, B)
当然也是可以执行的啦,而且需要的内存可比第一个减少了一半呢,但是我还是很不开心,好了,到这里我已经不知道自己在想什么了,可能就是想水一篇博客,我就把自己怎么瞎想的思路写下来啦。
我首先注意到,在第一个方法中的两个 mutate
有点类似于迭代,但是在并没有迭代的环境中,我遵循没有困难创造困难也要上的方式,将变出来一个迭代
# 第三个实现
swap <- function(data, x, y, i, j, n) {
if (n < 2) {
# 最后一步的行为
data %>% select({{y}}, {{x}})
}
else {
data %>%
mutate({{i}} := {{x}}, {{j}} := {{y}}) %>%
swap({{i}}, {{j}}, {{y}}, {{x}}, n-1)
}
}
我们来看思想实验一下结果
# 假象中运算过程一
data %>% swap(A, B, C, D, 3)
<==> data %>%
mutate(C = A, D = B) %>%
swap(C, D, B, A, 2)
<==> data %>%
mutate(C = A, D = B) %>%
mutate(B = C, A = D) %>%
swap(B, A, D, C, 1)
<==> data %>%
mutate(C = A, D = B) %>%
mutate(A = D, B = C) %>%
select(A, B)
可以看到最后一步就和第一个方案一样啦。你问我开心了没?当然是 不! 原因是这是一次性的过程,一次性的函数不要给他取名字! 名字是掺杂着感情和魔力的,以后抛弃他的时候,即便在说一万声谢谢,他也会非常非常伤心。于是就要用匿名函数,他必须是一个没有感情没有名字的工具人。但是匿名函数不能直接递归,但是没有关系,用 Y组合子。
首先是R中的Y组合子的定义。
# λf.(λx.xx)(λx.f(xx))
Y <- function(f) {
(function(x) {(x)(x)})(function(x) {f((x)(x))})
}
Y组合子是一个高阶函数,拥有一个非常绝妙的性质 \( Y(F) = F(Y(F)) \),Y组合子的证明自己随便推导一下就能出来了,我就不水了。所以可以把 swap
整个放进一个匿名函数中,然后用Y进行调用,这样就不需要在 swap
里面出现递归调用 swap
,而是由 Y组合子代为递归了。
data %>%
((function(f){
... #大致和 swap 差不多
}) %>% Y)(A,B,C,D,3)
这是我的思路啦,现在整理一下,用匿名函数构造一个长得像第二个方法一样的代码,整个代码就是
# 第四个实现
data %>% (function(data,K,L){
## 这里是最外面的一层皮
data %>% ((
function(f) { ## 这里是类似于 swap 的匿名函数
function(data, x, y, i, j, n) {
if (n < 2)
data %>% select({{y}}, {{x}})
else
data %>%
mutate({{i}} := {{x}}, {{j}} := {{y}}) %>%
f({{i}}, {{j}}, {{y}}, {{x}}, n - 1)
}
}) %>% ( ## 这里是 Y 组合子
function(f) {
(function(x) {(x)(x)})(function(x) {f((x)(x))})
}
))({{K}},{{L}},C,D,3)
})(A,B)
这就是本期全部啦,我们用构造递归,使用 Y组合子,解决了一个看上去很简单的问题,但是拯救了一个工具人的灵魂,他不会陷入被用完就扔掉的无限抑郁了。