2016-01-06 62 views
20

比方說,我有一個數字的以下向量:將數字向量彙總爲字符串的函數?

vec = c(1, 2, 3, 5, 7, 8, 9, 10, 11, 12) 

我正在尋找一個功能,將創建一個字符串總結號碼列表方式的人會,即

"1-3, 5, 7-12" 

我如何在R中做到這一點?

回答

28

添加另一種替代方法,您可以使用deparse ing方法。例如:

deparse(c(1L, 2L, 3L)) 
#[1] "1:3" 

趁着as.character 「deparse」 荷蘭國際集團給定的 「單子」 作爲輸入,我們可以使用:

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" 
+11

這是什麼巫術? –

+1

FWIW:如果輸入不是「字符」類型,則調用'as.character'是多餘的,因爲'gsub'開頭。 – Tensibai

+1

使用'fixed = TRUE'肯定會加快速度 –

21

我假設矢量按照例子排序。如果不事先使用vec <- sort(vec)

編輯說明:@DavidArenburg在我的原始答案中發現了一個錯誤,其中c(min(x), x)實際上應該是c(0, x)。由於我們現在知道我們總是需要首先添加0,所以我們可以省略創建x的第一步,並「即時」執行此操作。現在編輯原始答案和其他選項以反映(您可以檢查原始帖子的編輯歷史記錄)。謝謝大衛!

上調用unname的說明:我用unname(sapply(...)),以確保所得到的矢量未命名,否則將被命名爲0:第(n-1)其中n等於new_vec長度。正如@Tensibai在評論中正確指出的那樣,如果最終目的是通過運行toString(new_vec)生成長度爲1的字符向量,則無關緊要,因爲無論如何,向量名將被toString省略。


一個選項(可能不是最短的)將是:

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 = "-") 
})) 
+3

能使用'糊(獨特的(範圍(Y)),倒塌=' - ')'而不是'head'和'tail' – Zelazny7

+0

@ Zelazny7,這是個好主意,謝謝。我將把它作爲另一個選項加入 –

+1

對於'unname'調用,只要你在'toString'後面包括它們,'toString'或'paste0(..,collapse =「,」)'就不必要了不管怎樣都不要拿名字。 – Tensibai

7

EDITS:我加快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 
+1

我認爲通過toString替換外部paste0使得它更清潔(對於相同的結果),並且不調用unname,當將結果包裝到paste0中時,它實際上沒有興趣toString調用,所以也許它是增益來自的地方。 – Tensibai

+0

沒有真正的性能變化,但使用'toString'可能會失去選擇合攏字符的靈活性(例如,如果你想要「1-3; 5; 7-12」)。所以這似乎是一個偏好和實用性的問題。 – Benjamin

+0

它確實只是在這個特殊情況下保存了崩潰=「,」。我確實認爲這值得說:) – Tensibai