Hanna Dup
Hanna Dup

Reputation: 131

How to keep dropping the first value, until the sum of the vector is less than 20?

I am looking for a function which takes a vector and keeps dropping the first value until the sum of the vector is less than 20. Return the remaining values.

I've tried both a for-loop and while-loop and can't find a solution.

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)
  }
}

The expected output should be: 1,8,2,5 which is less than 20.

Upvotes: 10

Views: 1410

Answers (4)

Patrik_P
Patrik_P

Reputation: 3200

I would go with Reduce

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

Alternatively, define Reduce with function sum with the conditional argument na.rm = T in order to hanlde NAs if desired:

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

I find the Reduce option to start from right (end of the integer vector), and hence not having to reverse it first, convenient.

Upvotes: 1

Wimpel
Wimpel

Reputation: 27732

base solution without loops
not my most readable code ever, but it's pretty fast (see benchmarking below)

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

note: 'borrowed' the NA-handling from @Ronak's answer

sample data
vec = c(3, 2, NA, 4, 5, 1, 2, 3, 4, 9, NA, 1, 2)

benchmarks

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

Upvotes: 6

Ronak Shah
Ronak Shah

Reputation: 389047

Looking at the expected output it looks like you want to drop values until sum of remaining values is less than 20.

We can create a function

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

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

Trying it on another input

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

Breaking down the function, first the vec

vec = c(3,5,3,4,3,9,1,8,2,5)

We then reverse it

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

take cumulative sum over it (cumsum)

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

Find out number of enteries that are less than 20

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

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

and finally subset these last enteries using tail.


A slight modification in the code and it should be able to handle NAs as well

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

The logic being we replace NA with zeroes and then take the cumsum

Upvotes: 12

Sotos
Sotos

Reputation: 51592

You need to remove the first value each time, so your while loop should be,

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

#[1] 1 8 2 5

Upvotes: 7

Related Questions