2016-01-06 11 views
20

Sagen wir, ich die folgende Vektor von Zahlen haben:Funktion zum Zusammenfassen Vektor von Zahlen als String?

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

Ich bin für eine Funktion suchen, die eine Zeichenfolge Zusammenfassung der Liste von Zahlen, wie ein Mensch würde schaffen wird, das heißt

"1-3, 5, 7-12" 

Wie kann ich das in R machen?

Antwort

28

eine weitere Alternative Hinzufügen, könnten Sie eine deparse ing Ansatz. Zum Beispiel:

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

ausnutzend as.character "Deparse" ing eine gegebene "Liste" als Eingabe, könnten wir verwenden:

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

Welche Zauberei ist das? –

+1

FWIW: Der Aufruf von 'as.character' ist überflüssig, da' gsub' durch Aufruf aufgerufen wird, wenn die Eingabe nicht vom Typ "character" ist. – Tensibai

+1

Die Verwendung von 'fixed = TRUE' würde das definitiv beschleunigen –

21

Ich nehme an, dass der Vektor wie im Beispiel sortiert ist. Wenn nicht vorher vec <- sort(vec) verwenden.

Hinweis: @DavidArenburg entdeckte einen Fehler in meiner ursprünglichen Antwort, wo c(min(x), x) eigentlich c(0, x) sein sollte. Da wir jetzt wissen, dass wir immer zuerst eine 0 hinzufügen müssen, können wir den ersten Schritt der Erstellung x überspringen und es "on the fly" tun. Die ursprüngliche Antwort und zusätzliche Optionen werden jetzt bearbeitet, um dies zu berücksichtigen (Sie können den Bearbeitungsverlauf für den ursprünglichen Post überprüfen). Danke David!

Ein Hinweis auf Anrufe unname: Ich unname(sapply(...)) verwendet, um sicherzustellen, dass der resultierende Vektor nicht genannt wird, sonst wäre es 0 genannt werden: (n-1), wobei n die Länge des new_vec entspricht. Wie @Tensibai in den Kommentaren korrekt notiert, spielt es keine Rolle, ob das Endziel darin besteht, einen Vektor mit Länge 1 Zeichen zu erzeugen, wie er durch Ausführen von toString(new_vec) erzeugt wird, da Vektornamen sowieso von toString weggelassen werden.


Eine Option (möglicherweise nicht die kürzeste) wäre:

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

Ergebnis:

new_vec 
#[1] "1-3" "5" "7-12" 
toString(new_vec) 
#[1] "1-3, 5, 7-12" 

Dank @ Zelazny7 es unter Verwendung der range Funktion verkürzt werden kann :

new_vec <- unname(sapply(split(vec, c(0, cumsum(diff(vec) > 1))), function(y) { 
    paste(unique(range(y)), collapse='-') 
})) 

Dank @DavidArenburg es mithilfe tapply statt sapply + split weiter verkürzt werden kann:

new_vec <- unname(tapply(vec, c(0, cumsum(diff(vec) > 1)), function(y) { 
    paste(unique(range(y)), collapse = "-") 
})) 
+3

verwenden könnte 'Paste (unique (Bereich (y)), Zusammenbruch = '-') 'anstelle von' Kopf' und 'Schwanz' – Zelazny7

+0

@ Zelazny7, das ist eine nette Idee, danke.Ich werde es als eine andere Option hinzufügen –

+1

Für die 'unname' Aufrufe, solange Sie es in' toString' nachher wickeln, sind sie unnötig als 'toString' oder' paste0 (.., collapse = ",") 'wird nimm die Namen sowieso nicht. – Tensibai

7

EDITS: Ich so jetzt erste docendo den Code ein bisschen durch die Sortierung der Vektor beschleunigt, sie sind eigentlich gleichberechtigt.

Ich fügte auch Alexis Ansatz hinzu.

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

Ich denke, das Ersetzen der äußeren paste0 durch toString macht es sauberer (für das gleiche Ergebnis), und Sie rufen nicht name auf, der kein Interesse hat, wenn Sie das Ergebnis in eine paste0 oder umhüllen toString call, also kommt vielleicht die Verstärkung her. – Tensibai

+0

Keine wirkliche Leistungsänderung, aber die Verwendung von toString kann Ihnen die Flexibilität nehmen, Ihren Collapse-Charakter auszuwählen (zum Beispiel, wenn Sie "1-3; 5; 7-12" wollten). Es scheint also eine Frage der Präferenz und Nützlichkeit zu sein. – Benjamin

+0

Es ist tatsächlich nur, dass es den Kollaps = "," in diesem particularic Fall rettet. Ich denke, es lohnt sich gesagt zu werden :) – Tensibai

Verwandte Themen