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

Posted

技术标签:

【中文标题】如何继续删除第一个值,直到向量的总和小于 20?【英文标题】:How to keep dropping the first value, until the sum of the vector is less than 20? 【发布时间】:2019-06-10 05:53:41 【问题描述】:

我正在寻找一个函数,它接受一个向量并不断删除第一个值,直到向量的总和小于 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。

【问题讨论】:

【参考方案1】:

我会选择Reduce

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

或者,使用带有条件参数na.rm = T 的函数sum 定义Reduce,以便在需要时处理NA:

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 选项从右开始(整数向量的结尾),因此不必先反转它,方便。

【讨论】:

【参考方案2】:

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

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

注意:从@Ronak 的回答中“借用”NA-handling

样本数据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

【讨论】:

我想你可以在这里保存几个revvec[rev(cumsum(rev(replace(vec, is.na(vec), 0))) &lt; 20)]。这可能会进一步加快速度。 @markus 你说得非常非常正确.. 我想我在复制粘贴时有点太懒了......你只是减少了 30-40% 的执行时间! (请参阅答案中的更新基准)【参考方案3】:

查看预期输出,您似乎想要删除值,直到剩余值的总和小于 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 对这些最后的条目进行子集化。


对代码稍作修改,它应该也能处理NAs

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

逻辑是我们 replace NA 用零然后取 cumsum

【讨论】:

我认为这是迄今为止最快的解决方案,当然这只对更大的向量很重要。 @Phann 或一个大量的小向量:)。 仅供参考,您还应该在其中的某个地方处理 NA。顺便说一句好主意:) 我不知道为什么,但是当我尝试你的代码时,输​​出是整数(0) @HannaDup 一定是因为NAs 你现在可以看看更新的答案。【参考方案4】:

您每次都需要删除第一个值,所以您的while 循环应该是,

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


#[1] 1 8 2 5

【讨论】:

从 OP 的帖子中,可能看起来他们的实际数据中有 NA?如果是这样,记得定义sum(x, na.rm = TRUE) 好眼光!谢谢 没有被否决,但是很容易线性地做的事情的二次行为(不确定x &lt;- x[-1]在R中的行为,所以可能更糟)可能是原因。 @Voo 我不确定你所说的二次/线性是什么意思(我知道数学)。我假设您的意思是while 循环与不需要while。即使是这种情况,当我遵循 OP 的思路以显示错误时,投票仍然是有偏见的。这是一个糟糕的投票,但是人们会看到这一点。 x &lt;- x[-1] 也只是删除第一个值...没有复杂的行为 @Sotos 这不是 while 循环本身,而是求和、while 循环和删除之间的交互。我希望“仅删除第一个值”为 O(N),假设一个数组类型会导致整个行为为 O(N^3) 而不是 O(N)。现在我并不是说它总是不好(简单的代码通常很好,即使它比它必须要慢几个数量级),但我可以理解为什么有人会否决这样一个没有注意到这种行为的解决方案.

以上是关于如何继续删除第一个值,直到向量的总和小于 20?的主要内容,如果未能解决你的问题,请参考以下文章

对值求和,直到条件的下一次出现

我如何删除向量中的重复值,除了最后一个

编辑数字直到达到总和值

复制范围直到最后一行值等于 0

使用 Functor / Predicate 查找向量中小于其前任的第一个元素

二分搜索算法