2

I have a vector as follows:

playtimes <- c("1H18M20S", "1H27M5S", "18M27S", "56M38S", "21S")

and I want to convert these to playtimes in second. For example, the resulting vector would be something like this:

playtimeInSeconds <- c(4700, 5225, 1107, 3398, 21)

Im having trouble with separating the strings correctly based on the H, M and S. I wrote the following that works for the playtimes under 1 hour

minutes <- gsub("M.*", "", playtime)
seconds <- gsub(".*M", "", playtime) %>%
    gsub("S", "", .)

totalPlaytime <- as.numeric(minutes)*60 + as.numeric(seconds)

But Im not sure how to tackle the H portion of some strings.

Jacob
  • 406
  • 3
  • 19
  • 2
    Related: [Calculate character string "days, hours, minutes, seconds" to numeric total days](https://stackoverflow.com/questions/35087839/calculate-character-string-days-hours-minutes-seconds-to-numeric-total-days). E.g. `period_to_seconds(period(playtimes))` – Henrik Sep 07 '21 at 20:25

3 Answers3

2

You could strsplit and adapt the length of the list elements reversely to 3 which allows you to use sapply to get a matrix where you apply the matrix product %*%.

m <- sapply(strsplit(p, 'H|M|S'), \(x) as.double(rev(`length<-`(rev(x), 3))))
res <- as.vector(t(replace(m, is.na(m), 0)) %*% rbind(3600, 60, 1))
res
# [1] 4700 5225 1107 3398   21
jay.sf
  • 60,139
  • 8
  • 53
  • 110
1

interesting problem. here is a solution that potentially could be more efficient but does the job

# function from https://www.statworx.com/de/blog/strsplit-but-keeping-the-delimiter/
strsplit <- function(x,
                     split,
                     type = "remove",
                     perl = FALSE,
                     ...) {
  if (type == "remove") {
    # use base::strsplit
    out <- base::strsplit(x = x, split = split, perl = perl, ...)
  } else if (type == "before") {
    # split before the delimiter and keep it
    out <- base::strsplit(x = x,
                          split = paste0("(?<=.)(?=", split, ")"),
                          perl = TRUE,
                          ...)
  } else if (type == "after") {
    # split after the delimiter and keep it
    out <- base::strsplit(x = x,
                          split = paste0("(?<=", split, ")"),
                          perl = TRUE,
                          ...)
  } else {
    # wrong type input
    stop("type must be remove, after or before!")
  }
  return(out)
}


# convert to seconds
to_seconds <- c(H = 60 * 60,
                M = 60,
                S = 1)

get_seconds <- function(value, unit) {
  value * to_seconds[unit]
}


# example vector
playtimes <- c("1H18M20S", "1H27M5S", "18M27S", "56M38S", "21S")

# extract time parts
times <- strsplit(playtimes, 
                  split = "[A-Z]",
                  type = "after")
times
#> [[1]]
#> [1] "1H"  "18M" "20S"
#> 
#> [[2]]
#> [1] "1H"  "27M" "5S" 
#> 
#> [[3]]
#> [1] "18M" "27S"
#> 
#> [[4]]
#> [1] "56M" "38S"
#> 
#> [[5]]
#> [1] "21S"

# calculate each time in seconds
sapply(times,
       function(t) {
         # split numeric and unit part
         t_split <- strsplit(x = t,
                             split = "[A-Z]",
                             type = "before")
         
         # calculate seconds for each unit part
         times_in_seconds <- get_seconds(value = as.numeric(sapply(t_split, `[`, 1)),
                                         unit = sapply(t_split, `[`, 2))
         
         # sum of all parts
         sum(times_in_seconds)
       })
#> [1] 4700 5225 1107 3398   21
mnist
  • 6,571
  • 1
  • 18
  • 41
0

I followed the example given in the 3rd answer here and made the following

playtime <- sapply(playtime, function(x){paste(paste(rep(0, 3 - str_count(x, '[0-9]+')), collapse = ' '), x)})
totalPlaytime <- time_length(hms(playtime))

Short, sweet, and checks for potential errors where the playtime is less that 1 hr or less than 1 min.

Jacob
  • 406
  • 3
  • 19