将连续运行的数字折叠成一串范围
Collapse consecutive runs of numbers to a string of ranges
假设我有以下数字向量:
vec = c(1, 2, 3, 5, 7, 8, 9, 10, 11, 12)
我正在寻找一个函数来创建一个字符串,该字符串以人类的方式汇总数字列表。也就是说,每个 运行 的连续数字(这里是 1, 2, 3
和 7, 8, 9, 10, 11, 12
)被折叠成它的开始和结束值:
"1-3, 5, 7-12"
我如何在 R 中执行此操作?
我假设矢量按照示例中的方式排序。如果事先不使用vec <- sort(vec)
。
编辑说明:@DavidArenburg 在我原来的回答中发现了一个错误,其中 c(min(x), x)
实际上应该是 c(0, x)
。由于我们现在知道我们总是需要首先添加一个 0
,因此我们可以省略创建 x
的第一步并执行 "on the fly"。现在编辑原始答案和其他选项以反映这一点(您可以查看原始 post 的编辑历史记录)。谢谢大卫!
关于调用 unname
的注意事项:我使用 unname(sapply(...))
来确保未命名结果向量,否则它将被命名为 0:(n-1),其中 n 等于长度new_vec
。正如@Tensibai 在评论中正确指出的那样,如果最终目标是生成由 运行 toString(new_vec)
生成的长度为 1 的字符向量,这并不重要,因为 [=23] 将省略向量名称=] 无论如何。
一个选项(可能不是最短的)是:
new_vec <- unname(sapply(split(vec, c(0, cumsum(diff(vec) > 1))), function(y) {
if(length(y) == 1) y else paste0(head(y, 1), "-", tail(y, 1))
}))
结果:
new_vec
#[1] "1-3" "5" "7-12"
toString(new_vec)
#[1] "1-3, 5, 7-12"
感谢@Zelazny7,可以使用 range
函数缩短它:
new_vec <- unname(sapply(split(vec, c(0, cumsum(diff(vec) > 1))), function(y) {
paste(unique(range(y)), collapse='-')
}))
感谢@DavidArenburg,可以使用 tapply
代替 sapply
+ split
:
进一步缩短
new_vec <- unname(tapply(vec, c(0, cumsum(diff(vec) > 1)), function(y) {
paste(unique(range(y)), collapse = "-")
}))
编辑:我通过先对向量进行排序大大加快了 docendo 的代码速度,所以现在它们实际上处于同等地位。
我还添加了 alexis 的方法。
readable_integers <- function(integers)
{
integers <- sort(unique(integers))
group <- cumsum(c(0, diff(integers)) != 1)
paste0(vapply(split(integers, group),
function(x){
if (length(x) == 1) as.character(x)
else paste0(range(x), collapse = "-")
},
character(1)),
collapse = "; ")
}
library(microbenchmark)
vec = c(1, 2, 3, 5, 7, 8, 9, 10, 11, 12)
microbenchmark(
docendo = {vec <- sort(vec)
x <- cumsum(diff(vec) > 1)
toString(tapply(vec, c(min(x), x), function(y) paste(unique(range(y)), )collapse = "-"))
},
Benjamin = readable_integers(vec),
alexis = {vec <- sort(vec)
as.character(split(as.integer(vec), cumsum(c(TRUE, diff(vec) != 1))))
toString(gsub(":", "-", .Last.value))}
)
Unit: microseconds
expr min lq mean median uq max neval
docendo 205.273 220.3755 230.3134 228.293 235.4780 467.142 100
Benjamin 121.991 128.4420 135.5302 133.574 143.3980 161.286 100
alexis 121.698 128.0030 137.0374 136.507 143.3975 169.790 100
set.seed(pi)
vec = sample(1:1000, 900)
set.seed(pi)
vec = sample(1:1000, 900)
microbenchmark(
docendo = {vec <- sort(vec)
x <- cumsum(diff(vec) > 1)
toString(tapply(sort(vec), c(min(x), x), function(y) paste(unique(range(y)), collapse = "-")))
},
Benjamin = readable_integers(vec),
alexis = {vec <- sort(vec)
as.character(split(as.integer(vec), cumsum(c(TRUE, diff(vec) != 1))))
toString(gsub(":", "-", .Last.value))}
)
Unit: microseconds
expr min lq mean median uq max neval
docendo 1307.294 1353.7735 1420.3088 1379.7265 1427.8190 2554.473 100
Benjamin 615.525 626.8155 661.2513 638.8385 665.3765 1676.493 100
alexis 799.684 808.3355 866.1516 820.0650 833.2615 1974.138 100
添加另一种选择,您可以使用 deparse
ing 方法。例如:
deparse(c(1L, 2L, 3L))
#[1] "1:3"
利用 as.character
"deparse" 给定的 "list" 作为输入,我们可以使用:
as.character(split(as.integer(vec), cumsum(c(TRUE, diff(vec) != 1))))
#[1] "1:3" "5" "7:12"
toString(gsub(":", "-", .Last.value))
#[1] "1-3, 5, 7-12"
假设我有以下数字向量:
vec = c(1, 2, 3, 5, 7, 8, 9, 10, 11, 12)
我正在寻找一个函数来创建一个字符串,该字符串以人类的方式汇总数字列表。也就是说,每个 运行 的连续数字(这里是 1, 2, 3
和 7, 8, 9, 10, 11, 12
)被折叠成它的开始和结束值:
"1-3, 5, 7-12"
我如何在 R 中执行此操作?
我假设矢量按照示例中的方式排序。如果事先不使用vec <- sort(vec)
。
编辑说明:@DavidArenburg 在我原来的回答中发现了一个错误,其中 c(min(x), x)
实际上应该是 c(0, x)
。由于我们现在知道我们总是需要首先添加一个 0
,因此我们可以省略创建 x
的第一步并执行 "on the fly"。现在编辑原始答案和其他选项以反映这一点(您可以查看原始 post 的编辑历史记录)。谢谢大卫!
关于调用 unname
的注意事项:我使用 unname(sapply(...))
来确保未命名结果向量,否则它将被命名为 0:(n-1),其中 n 等于长度new_vec
。正如@Tensibai 在评论中正确指出的那样,如果最终目标是生成由 运行 toString(new_vec)
生成的长度为 1 的字符向量,这并不重要,因为 [=23] 将省略向量名称=] 无论如何。
一个选项(可能不是最短的)是:
new_vec <- unname(sapply(split(vec, c(0, cumsum(diff(vec) > 1))), function(y) {
if(length(y) == 1) y else paste0(head(y, 1), "-", tail(y, 1))
}))
结果:
new_vec
#[1] "1-3" "5" "7-12"
toString(new_vec)
#[1] "1-3, 5, 7-12"
感谢@Zelazny7,可以使用 range
函数缩短它:
new_vec <- unname(sapply(split(vec, c(0, cumsum(diff(vec) > 1))), function(y) {
paste(unique(range(y)), collapse='-')
}))
感谢@DavidArenburg,可以使用 tapply
代替 sapply
+ split
:
new_vec <- unname(tapply(vec, c(0, cumsum(diff(vec) > 1)), function(y) {
paste(unique(range(y)), collapse = "-")
}))
编辑:我通过先对向量进行排序大大加快了 docendo 的代码速度,所以现在它们实际上处于同等地位。
我还添加了 alexis 的方法。
readable_integers <- function(integers)
{
integers <- sort(unique(integers))
group <- cumsum(c(0, diff(integers)) != 1)
paste0(vapply(split(integers, group),
function(x){
if (length(x) == 1) as.character(x)
else paste0(range(x), collapse = "-")
},
character(1)),
collapse = "; ")
}
library(microbenchmark)
vec = c(1, 2, 3, 5, 7, 8, 9, 10, 11, 12)
microbenchmark(
docendo = {vec <- sort(vec)
x <- cumsum(diff(vec) > 1)
toString(tapply(vec, c(min(x), x), function(y) paste(unique(range(y)), )collapse = "-"))
},
Benjamin = readable_integers(vec),
alexis = {vec <- sort(vec)
as.character(split(as.integer(vec), cumsum(c(TRUE, diff(vec) != 1))))
toString(gsub(":", "-", .Last.value))}
)
Unit: microseconds
expr min lq mean median uq max neval
docendo 205.273 220.3755 230.3134 228.293 235.4780 467.142 100
Benjamin 121.991 128.4420 135.5302 133.574 143.3980 161.286 100
alexis 121.698 128.0030 137.0374 136.507 143.3975 169.790 100
set.seed(pi)
vec = sample(1:1000, 900)
set.seed(pi)
vec = sample(1:1000, 900)
microbenchmark(
docendo = {vec <- sort(vec)
x <- cumsum(diff(vec) > 1)
toString(tapply(sort(vec), c(min(x), x), function(y) paste(unique(range(y)), collapse = "-")))
},
Benjamin = readable_integers(vec),
alexis = {vec <- sort(vec)
as.character(split(as.integer(vec), cumsum(c(TRUE, diff(vec) != 1))))
toString(gsub(":", "-", .Last.value))}
)
Unit: microseconds
expr min lq mean median uq max neval
docendo 1307.294 1353.7735 1420.3088 1379.7265 1427.8190 2554.473 100
Benjamin 615.525 626.8155 661.2513 638.8385 665.3765 1676.493 100
alexis 799.684 808.3355 866.1516 820.0650 833.2615 1974.138 100
添加另一种选择,您可以使用 deparse
ing 方法。例如:
deparse(c(1L, 2L, 3L))
#[1] "1:3"
利用 as.character
"deparse" 给定的 "list" 作为输入,我们可以使用:
as.character(split(as.integer(vec), cumsum(c(TRUE, diff(vec) != 1))))
#[1] "1:3" "5" "7:12"
toString(gsub(":", "-", .Last.value))
#[1] "1-3, 5, 7-12"