如何持续删除向量中的第一个值,直到向量的总和小于20?

10
我正在寻找一个函数,它接受一个向量并不断删除第一个值,直到向量的总和小于20。返回剩余的值。
我尝试了for循环和while循环,但都找不到解决方案。
vec <- c(3,5,3,4,3,9,1,8,2,5)

short <- function(vec){

 for (i in 1:length(vec)){
    while (!is.na((sum(vec)) < 20)){
      vec <- vec[i+1:length(vec)]
      #vec.remove(i)
  }
}

期望的输出应为:1,8,2,5,小于20。
4个回答

12

从预期输出来看,您想要删除值,直到剩余值的总和小于20。

我们可以创建一个函数。

drop_20 <- function(vec) {
  tail(vec, sum(cumsum(rev(vec)) < 20))
}

drop_20(vec)
#[1] 1 8 2 5

尝试在另一个输入上运行

drop_20(1:10)
#[1]  9 10

分解这个函数,首先是vec
vec = c(3,5,3,4,3,9,1,8,2,5)

然后我们将其reverse

rev(vec)
#[1] 5 2 8 1 9 3 4 3 5 3

对其进行累加和操作(cumsum

cumsum(vec)
#[1]  3  8 11 15 18 27 28 36 38 43

查找少于20的条目数

cumsum(rev(vec)) < 20
 #[1]  TRUE  TRUE  TRUE  TRUE FALSE FALSE FALSE FALSE FALSE FALSE

sum(cumsum(rev(vec)) < 20)
#[1] 4

最后,使用tail这个函数来选取最后的条目。


对代码进行轻微修改,就可以处理NA值。

drop_20 <- function(vec) {
   tail(vec, sum(cumsum(replace(rev(vec), is.na(rev(vec)), 0)) < 20))
}

vec = c(3, 2, NA, 4, 5, 1, 2, 3, 4, 9, NA, 1, 2)
drop_20(vec)
#[1]  3  4  9 NA  1  2

逻辑是我们用零替换 NA,然后进行 cumsum


我认为这是目前最快的解决方案,当然这只在较大的向量上才有意义。 - Phann
@Phann 或者许多小向量之一 :)。 - s_baldur
@HannaDup 可能是因为 NA,你现在可以查看更新后的答案。 - Ronak Shah
另一个类似的代码:tail(vec, - findInterval(0, cumsum(vec) - 20) - 1L)... 这需要满足 vec >= 0。 - Frank
@Ronak 嗯,是的,我不确定如何扩展到那种情况。 - Frank
显示剩余3条评论

7

您需要每次删除第一个值,因此您的while循环应该如下:

while (sum(x, na.rm = TRUE) >= 20) {
    x <- x[-1]
}

#[1] 1 8 2 5

1
从原帖来看,可能实际数据中有NA值?如果是这样,请记得定义sum(x, na.rm = TRUE) - Khaynes
好眼力!谢谢。 - Sotos
没有被downvote,但二次行为(不确定在R中x <- x[-1]的行为如何,所以可能更糟)可能是某些东西很容易线性处理的原因。 - Voo
@Voo 我不确定你所说的二次/线性是什么意思(我知道数学)。我猜你是指 while 循环与不需要 while 的区别。即使是这种情况,downvote 仍然是有偏见的,因为我按照 OP 的思路来展示错误。无论如何看待这个问题,这都是一个不好的 downvote。另外,x <- x[-1] 只是删除第一个值...没有复杂的行为。 - Sotos
2
@Sotos,问题不在于while循环本身,而在于求和、while循环和删除之间的交互。如果假设一个种类的数组会导致整个行为变成O(N ^ 3)而不是O(N),那么我会期望“只删除第一个值”是O(N)。现在我并不是说这总是不好的(即使代码比必须慢得多,简单的代码通常也可以),但我可以理解为什么有人会对这样的解决方案进行投票,因为它没有注意到这种行为。 - Voo

6

没有循环的基本解决方案
这并不是我最易读的代码,但它非常快(见下面的基准测试)

rev( rev(vec)[cumsum( replace( rev(vec), is.na( rev(vec) ), 0 ) ) < 20] )
#[1] 1 8 2 5

注意:从@Ronak的答案中“借用”了NA处理方法

示例数据
vec = c(3, 2, NA, 4, 5, 1, 2, 3, 4, 9, NA, 1, 2)

基准测试

microbenchmark::microbenchmark(
  Sotos = { 
    while (sum(vec, na.rm = TRUE) >= 20) {
      vec <- vec[-1] 
    } 
  },
  Ronak = tail(vec, sum(cumsum(replace(rev(vec), is.na(rev(vec)), 0)) < 20)),
  Wimpel = rev( rev(vec)[cumsum( replace( rev(vec), is.na( rev(vec) ), 0 ) ) < 20]),
  WimpelMarkus = vec[rev(cumsum(rev(replace(vec, is.na(vec), 0))) < 20)]
  )


# Unit: microseconds
#         expr      min       lq       mean    median        uq      max neval
#        Sotos 2096.795 2127.373 2288.15768 2152.6795 2425.4740 3071.684   100
#        Ronak   30.127   33.440   42.54770   37.2055   49.4080  101.827   100
#       Wimpel   13.557   15.063   17.65734   16.1175   18.5285   38.261   100
# WimpelMarkus    7.532    8.737   12.60520   10.0925   15.9680   45.491   100

1
我认为你可以在这里节省几个 revvec[rev(cumsum(rev(replace(vec, is.na(vec), 0))) < 20)]。这可能会进一步提高速度。 - markus
1
@markus,你说得非常对。我在复制粘贴方面有点懒惰了...你刚刚将执行时间缩短了30-40%!(请参见答案中更新的基准测试) - Wimpel

1
我会选择 Reduce
vec[Reduce(f = "+", x = vec, accumulate = T, right = T) < 20]
##[1] 1 8 2 5

或者,使用带有条件参数na.rm = T的函数sum定义Reduce以处理NAs(如果需要):

vec2 <- c(3, 2, NA, 4, 5, 1, 2, 3, 4, 9, NA, 1, 2)
vec2[Reduce(f = function(a,b) sum(a, b, na.rm = T), x = vec2, accumulate = TRUE, right = T) < 20]
##[1]  3  4  9 NA  1  2

我发现“Reduce”选项从右侧(整数向量的末尾)开始,因此无需先反转它,这很方便。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接