我正在从另一个平台读取数据,其中使用下面列出的字符串组合来表示时间戳:
\* = current time
t = current day (00:00)
mo = month
d = days
h = hours
m = minutes
例如,*-3d
是当前时间减去 3 天,t-3h
是今天早上(昨天午夜)之前的三个小时。
我希望能够将这些方程引入 R 并获得相应的 POSIXct 值。我尝试在下面的函数中使用正则表达式,但丢失了每个字符串的数字乘数:
strTimeConverter <- function(z){
ret <- stringi::stri_replace_all_regex(
str = z,
pattern = c('^\\*',
'^t',
'([[:digit:]]{1,})mo',
'([[:digit:]]{1,})d',
'([[:digit:]]{1,})h',
'([[:digit:]]{1,})m'),
replacement = c('Sys.time()',
'Sys.Date()',
'*lubridate::months(1)',
'*lubridate::days(1)',
'*lubridate::hours(1)',
'*lubridate::minutes(1)'),
vectorize_all = F
)
return(ret)
# return(eval(expr = parse(text = ret)))
}
> strTimeConverter('*-5mo+3d+4h+2m')
[1] "Sys.time()-*lubridate::months(1)+*lubridate::days(1)+*lubridate::hours(1)+*lubridate::minutes(1)"
> strTimeConverter('t-5mo+3d+4h+2m')
[1] "Sys.Date()-*lubridate::months(1)+*lubridate::days(1)+*lubridate::hours(1)+*lubridate::minutes(1)"
预期输出:
# *-5mo+3d+4h+2m
"Sys.time()-5*lubridate::months(1)+3*lubridate::days(1)+4*lubridate::hours(1)+4*lubridate::minutes(1)"
# t-5mo+3d+4h+2m
"Sys.Date()-5*lubridate::months(1)+3*lubridate::days(1)+4*lubridate::hours(1)+4*lubridate::minutes(1)"
我认为将 [[:digit]]{1,}
括在括号 ()
中会保留它们,但显然这是行不通的。我像这样定义了模式,否则代码会替换重复出现的内容,例如*
转换为 Sys.time()
但随后 Sys.time()
中的 m
被替换为*lubridate::分钟(1)
。
我计划使用 eval(parse(text = ...))
将(预期)输出转换为 R 日期时间 - 目前已在函数中注释掉。
我愿意使用其他软件包或方法。
更新
经过一番修改后,我发现以下版本有效 - 我按顺序替换字符串,以便新替换的字符不会再次被替换:
strTimeConverter <- function(z){
ret <- stringi::stri_replace_all_regex(
str = z,
pattern = c('y', 'd', 'h', 'mo', 'm', '^t', '^\\*'),
replacement = c('*years(1)',
'*days(1)',
'*hours(1)',
'*days(30)',
'*minutes(1)',
'Sys.Date()',
'Sys.time()'),
vectorize_all = F
)
ret <- gsub(pattern = '\\*', replacement = '*lubridate::', x = ret)
rdate <- (eval(expr = parse(text = ret)))
attr(rdate, 'tzone') <- 'UTC'
return(rdate)
}
sample_string <- '*-5mo+3d+4h+2m'
strTimeConverter(sample_string)
这可以工作,但不是很优雅,并且可能会失败,因为我被迫合并其他表达式(例如 yd
表示一年中的某一天,例如 124)。
最佳答案
您可以在替换中使用反向引用,如下所示:
library(stringr)
x <- c("*-5mo+3d+4h+2m", "t-5mo+3d+4h+2m")
repl <- c('^\\*' = 'Sys.time()', '^t' = 'Sys.Date()', '(\\d+)mo' = '\\1*lubridate::months(1)', '(\\d+)d' = '\\1*lubridate::days(1)', '(\\d+)h' = '\\1*lubridate::hours(1)', '(\\d+)m' = '\\1*lubridate::minutes(1)')
stringr::str_replace_all(x, repl)
## => [1] "Sys.time()-5*lubridate::months(1)+3*lubridate::days(1)+4*lubridate::hours(1)+2*lubridate::minutes(1)"
## [2] "Sys.Date()-5*lubridate::months(1)+3*lubridate::days(1)+4*lubridate::hours(1)+2*lubridate::minutes(1)"
请参阅R demo online .
例如,请参见'(\\d+)mo' = '\\1*lubridate::months(1)'
。此处,(\d+)mo
匹配并捕获到组 1 一个或多个数字,并且 mo
只是匹配。然后,当找到匹配项时,\1*lubridate::months(1)
中的 \1
会将第 1 组的内容插入到结果字符串中。
请注意,如果您使用右侧的单词边界 (\b
) 限制时间段匹配,可能会使替换更安全:
repl <- c('^\\*' = 'Sys.time()', '^t' = 'Sys.Date()', '(\\d+)mo\\b' = '\\1*lubridate::months(1)', '(\\d+)d\\b' = '\\1*lubridate::days(1)', '(\\d+)h\\b' = '\\1*lubridate::hours(1)', '(\\d+)m\\b' = '\\1*lubridate::minutes(1)')
如果时间跨度在没有任何非单词分隔符的情况下相互粘合,则该方法将不起作用,但示例字符串中有 +
,因此这里是安全的。
实际上,您也可以使其与您使用的功能一起使用。只需确保反向引用具有 $n
语法:
x <- c("*-5mo+3d+4h+2m", "t-5mo+3d+4h+2m")
pattern = c('^\\*', '^t', '(\\d+)mo', '(\\d+)d', '(\\d+)h', '(\\d+)m')
replacement = c('Sys.time()', 'Sys.Date()', '$1*lubridate::months(1)', '$1*lubridate::days(1)', '$1*lubridate::hours(1)', '$1*lubridate::minutes(1)')
stringi::stri_replace_all_regex(x, pattern, replacement, vectorize_all=FALSE)
输出:
[1] "Sys.time()-5*lubridate::months(1)+3*lubridate::days(1)+4*lubridate::hours(1)+2*lubridate::minutes(1)"
[2] "Sys.Date()-5*lubridate::months(1)+3*lubridate::days(1)+4*lubridate::hours(1)+2*lubridate::minutes(1)"
关于将时间方程转换为 R 日期时间的正则表达式 (POSIXct),我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/65362829/