【问题标题】:R: Adding zeroes after old zeroes in a vector?R:在向量中的旧零之后添加零?
【发布时间】:2025-11-25 19:05:02
【问题描述】:

假设我有一个带有 1 和 0 的向量

我写得很紧凑:

1111111100001111111111110000000001111111111100101

我需要获取一个新向量,将零之后的“N”个向量替换为新零。

例如 N = 3。

1111111100001111111111110000000001111111111100101变成 1111111100000001111111110000000000001111111100000

我可以用 for 循环来做到这一点,但我读过这不是一个好习惯,那我该怎么做呢?

干杯

我的矢量确实是一个动物园系列,但我想它没有任何区别。 如果我想要零到最后,我会使用 cumprod。

【问题讨论】:

  • 谢谢大家。我发现的最快的工作代码是乔纳森的下面的代码。
  • 你应该把它标记为接受。

标签: r vector zero


【解决方案1】:

这是一种方法:

> tmp <- strsplit('1111111100001111111111110000000001111111111100101','')
> tmp <- as.numeric(unlist(tmp))
> 
> n <- 3
> 
> tmp2 <- embed(tmp, n+1)
> 
> tmp3 <- tmp
> tmp3[ which( apply( tmp2, 1, function(x) any(x==0) ) ) + n ] <- 0
> 
> paste(tmp3, collapse='')
[1] "1111111100000001111111110000000000001111111100000"

这是否比循环更好取决于您。

如果那里有 0,这也不会改变第一个 n 元素。

这是另一种方式:

> library(gtools)
> 
> tmpfun <- function(x) {
+ if(any(x==0)) {
+ 0
+ } else {
+ x[length(x)]
+ }
+ }
> 
> tmp4 <- running( tmp, width=4, fun=tmpfun, 
+ allow.fewer=TRUE )
> 
> tmp4 <- unlist(tmp4)
> paste(tmp4, collapse='')
[1] "1111111100000001111111110000000000001111111100000"
> 

【讨论】:

    【解决方案2】:

    您也可以使用rle 执行此操作。您需要做的就是将 n 添加到值为 0 的所有长度,并在值为 1 时减去 n(当连续少于 n 个时要小心一点)。 (使用Greg的方法构建样本)

    rr <- rle(tmp)
    ## Pad so that it always begins with 1 and ends with 1
    if (rr$values[1] == 0) {
       rr$values <- c(1, rr$values)
       rr$lengths <- c(0, rr$lengths)  
    }
    if (rr$values[length(rr$values)] == 0) {
      rr$values <- c(rr$values, 1)
      rr$lengths <- c(rr$lengths, 0)  
    }
    zero.indices <- seq(from=2, to=length(rr$values), by=2)
    one.indices <- seq(from=3, to=length(rr$values), by=2)
    rr$lengths[zero.indices] <- rr$lengths[zero.indices] + pmin(rr$lengths[one.indices], n)
    rr$lengths[one.indices] <- pmax(0, rr$lengths[one.indices] - n)
    inverse.rle(rr)
    

    【讨论】:

    • 为什么要更改最后一个零??我以为我可以做得更容易,你的答案很复杂。您可以在这里阅读不同的方法r.789695.n4.nabble.com/…,但它不能按预期工作,谢谢
    • 我的向量总是从 1 开始。我也可以尝试将向量的元素移动一个位置并将结果与​​原始结果进行与运算。并再次将 2 个位置等移动到 N。但这非常慢。我找到了一种更快的方法,移动一个位置,然后是 2,然后是 4,然后是 8.... 并做 ANDs
    • 如果你知道你的向量以一个开头,你可以去掉第一个 if。你需要第二个 if 因为后续行基本上每个 0 序列都向前看下一个 1 序列,如果没有尾随 1 序列,这将失败。
    【解决方案3】:

    跟进我之前的评论,如果速度确实是一个问题 - 将向量转换为字符串并使用正则表达式可能比其他解决方案更快。首先是一个函数:

    replaceZero <- function(x,n){
        x <- gsub(paste("01.{",n-1,"}", sep = "") , paste(rep(0,n+1),collapse = ""), x)
    }
    

    生成数据

    z <- sample(0:1, 1000000, replace = TRUE)
    
    z <- paste(z, collapse="")
    repz <- replaceZero(z,3)
    repz <- as.numeric(unlist(strsplit(repz, "")))
    

    系统崩溃、运行正则表达式和拆分回向量的时间:

    Regex method
       user  system elapsed 
       2.39    0.04    2.39 
    Greg's method
       user  system elapsed 
       17.m39    0.17   18.30
    Jonathon's method
       user  system elapsed 
       2.47    0.02    2.31 
    

    【讨论】:

    • 您好,我已经尝试过您的解决方案,但效果不佳。乔纳森的。
    • @user425895 - 什么不符合您的预期?没有给你想要的答案?耗时太长?按键时感觉不对劲?不能很好地工作并不是很有帮助,如果代码有问题 - 知道为什么它不能产生你想要的结果会让我修复它以便它产生 - 那些出现类似问题的人可以可以访问可以工作的代码 sn-ps……“不能很好地工作”不会让任何人更接近那个目标。
    • 如果使用这个向量,结果不正确 x
    【解决方案4】:
    x <- c(1,1,1,1,1,1,1,1,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,0,0,1,1,0,1)
    
    n <- 3
    z<-rle(x)
    tmp <- cumsum(z$lengths)
    
    for (i in seq(which.min(z$values),max(which(z$values==1)),2)) {
             if  (z$lengths[i+1] < n)   x[tmp[i]:(tmp[i] + z$lengths[i+1])] <- 0
             else                       x[tmp[i]:(tmp[i]+n)] <- 0
    }
    

    【讨论】:

      【解决方案5】:

      只循环遍历(假设很少)N 个实例怎么样:

      addZeros <- function(x, N = 3) {
          xx <- x
          z <- x - 1
          for (i in 1:N) {
              xx <- xx + c(rep(0, i), z[-c((NROW(x) - i + 1):NROW(x))])
          }
          xx[xx<0] <- 0
          xx
      }
      

      只需将所有零实例变为 -1 以减去 N 个后续值。

      > x <- c(1,1,1,1,1,1,1,1,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,0,0,1,0,1)
      > x
       [1] 1 1 1 1 1 1 1 1 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 1 1 1 1 1
      [39] 1 1 1 1 1 1 0 0 1 0 1
      > addZeros(x)
       [1] 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 1 1
      [39] 1 1 1 1 1 1 0 0 0 0 0
      

      编辑:

      在阅读了您对 R-help 邮件列表中数据的描述后,这显然不是小 N 的情况。因此,您可能需要为此考虑使用 C 函数。

      在文件“addZeros.c”中:

      void addZeros(int *x, int *N, int *n)
      {
          int i, j;
      
          for (i = *n - 1; i > 0; i--)
          {
              if ((x[i - 1] == 0) && (x[i] == 1))
              {
                  j = 0;
                  while ((j < *N) && (i + j < *n) && (x[i + j] == 1))
                  {
                      x[i + j] = 0;
                      j++;
                  }
              }
          }
      }
      

      在命令提示符下(Windows 中的 MS DOS,按 Win+r 并输入 cmd),输入“R CMD SHLIB addZeros.c”。如果无法到达 R 的路径(即“未知的 kommand R”),您需要说明完整地址(在我的系统上:

      "c:\Program Files\R\R-2.10.1\bin\R.exe" CMD SHLIB addZeros.c
      

      在 Windows 上,这应该会生成一个 DLL(在 Linux 中是 .so),但如果您还没有 R 工具箱,您应该下载并安装它(它是工具的集合,例如 Perl 和 Mingw)。从下载最新版本 http://www.murdoch-sutherland.com/Rtools/

      用于此的 R 包装函数是:

      addZeros2 <- function(x, N) {
          if (!is.loaded("addZeros"))
              dyn.load(file.path(paste("addZeros", .Platform$dynlib.ext, sep = "")))
          .C("addZeros",
              x = as.integer(x),
              as.integer(N),
              as.integer(NROW(x)))$x
      }
      

      请注意,在第一次调用 addZeros R 函数之前,R 中的工作目录应该与 DLL 相同(在我的系统上 setwd("C:/Users/eyjo/Documents/Forrit/R/addZeros"))(或者,在 dyn.load 中只包含 dll 的完整路径文件)。最好将它们保存在项目下的子目录中(即“c”),然后在文件路径中的“addZeros”前面添加“c/”。

      举例说明:

      > x <- rbinom(1000000, 1, 0.9)
      >
      > system.time(addZeros(x, 10))
         user  system elapsed 
         0.45    0.14    0.59 
      > system.time(addZeros(x, 400))
         user  system elapsed 
        15.87    3.70   19.64 
      > 
      > system.time(addZeros2(x, 10))
         user  system elapsed 
         0.01    0.02    0.03 
      > system.time(addZeros2(x, 400))
         user  system elapsed 
         0.03    0.00    0.03 
      > 
      

      “addZeros”是我最初的建议,只有内部 R,而 addZeros2 使用的是 C 函数。

      【讨论】:

      • 我喜欢看到你们不同的创意方式。
      • 嗨。如何在 Windows 上编译它?
      • 我添加了更多解释。您应该安装工具箱:murdoch-sutherland.com/Rtools
      【解决方案6】:

      我真的很喜欢为此使用“正则表达式”的想法,所以我投了赞成票。 (希望我也得到了一个正确的答案,并从嵌入和运行的答案中学到了一些东西。整洁!)这是 Chase 答案的一个变体,我认为可以解决所提出的问题:

      replaceZero2 <- function(x, n) {
        if (n == 0) {
          return(x)
        }
        xString <- paste(x, collapse="")
        result <- gsub(paste("(?<=",
                   paste("01{", 0:(n - 1), "}", sep="", collapse="|"),
                   ")1", sep=""),
             "0", xString, perl=TRUE)
        return(as.numeric(unlist(strsplit(result, ""))))
      }
      

      这似乎与在 gd047 的示例输入中 n = 1,2,3,4,5 的 Chang 的 rle 方法产生相同的结果。

      也许您可以使用 \K 更简洁地编写此代码?

      【讨论】:

      • +1 这行得通。我也喜欢使用“正则表达式”的想法。尽管如此,乔纳森的想法更好(而且更快)。
      【解决方案7】:

      我自己找到了解决方案。 我认为这很容易而且不是很慢。 我想如果有人可以用 C++ 编译它会非常快,因为它只有一个循环。

      f5 <- function(z, N) {
         x <- z
         count <- 0
         for (i in 1:length(z)) {
           if (z[i]==0) { count <- N }
           else {
             if (count >0) { 
                x[i] <- 0  
                count <- count-1 }
         }
      }
      x
      }
      

      【讨论】:

        【解决方案8】:

        使用移动最小值函数非常快速、简单,并且不依赖于跨度的分布:

        x <- rbinom(1000000, 1, 0.9)
        system.time(movmin(x, 3, na.rm=T))
        # user  system elapsed 
        # 0.11    0.02    0.13 
        

        以下 movmin 的简单定义就足够了(完整的函数在这种情况下有一些多余的功能,例如对大 N 使用 van Herk/Gil-Werman 算法)

        movmin = function(x, n, na.rm=F) {
          x = c(rep.int(NA, n - 1), x) # left pad
          do.call(pmin, c(lapply(1:n, function(i) x[i:(length(x) - n + i)]), na.rm=na.rm))
        }
        

        实际上,您需要 4 的窗口大小,因为您会影响 0 之后的 3 个值。这与您的 f5 匹配:

        x <- rbinom(1000000, 1, 0.9)
        all.equal(f5(x, 3), movmin(x, 4, na.rm=T))
        # [1] TRUE
        

        【讨论】:

        • 速度很快,但没有给出正确的答案
        • 啊,是的,我看到了不同之处——窗口大小需要扩大(见上面的附录)。