Botanophobia::Logging

瞎整

R 中的位置交换

标签: Y组合子 , 工具人 以及 R

最近在处理很多数据,R 中我是一直使用简洁好看好理解的 tidyverse , 然后遇到一个问题,比如说我们有数据

AB
1String1
2String2

希望将 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组合子,解决了一个看上去很简单的问题,但是拯救了一个工具人的灵魂,他不会陷入被用完就扔掉的无限抑郁了。