2016-01-06 13 views

risposta

28

Aggiunta di un'altra alternativa, è possibile utilizzare un approccio deparse. Per esempio:

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

vantaggio di as.character "Deparse" Prendendo ing un dato "lista" come input, potremmo usare:

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

Che stregoneria è questa? –

+1

FWIW: la chiamata a 'as.character' è superflua quando' gsub' inizia a chiamarlo se l'input non è di tipo "character". – Tensibai

+1

L'uso di 'fixed = TRUE' lo renderebbe sicuramente più veloce –

21

Suppongo che il vettore sia ordinato come nell'esempio. In caso contrario, utilizzare vec <- sort(vec) in anticipo.

Modifica nota: @DavidArenburg ha individuato un errore nella mia risposta originale dove c(min(x), x) in realtà dovrebbe essere c(0, x). Dato che ora sappiamo che abbiamo sempre bisogno di aggiungere uno 0, possiamo omettere il primo passaggio della creazione di x e farlo "al volo". La risposta originale e le opzioni aggiuntive ora vengono modificate per rispecchiare tale aspetto (puoi controllare la cronologia delle modifiche per il post originale). Grazie David!

Una nota sulle chiamate verso unname: ho usato unname(sapply(...)) per assicurare che il vettore risultante non ha un nome, altrimenti sarebbe chiamato 0: (n-1), dove n è uguale alla lunghezza del new_vec. Come @Tensibai ha annotato correttamente nei commenti, non importa se l'obiettivo finale è generare un vettore di lunghezza 1 come prodotto eseguendo toString(new_vec) poiché i nomi vettoriali verranno omessi dallo toString.


Una possibilità (forse non più breve) sarebbe:

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

risultati:

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

Grazie a @ Zelazny7 può essere accorciato utilizzando la funzione range :

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

Grazie a @DavidArenburg può essere ulteriormente ridotto utilizzando tapply anziché sapply + split:

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

potrebbe usare 'pasta (unica (gamma (y)), collasso = '-') 'invece di' testa' e 'coda' – Zelazny7

+0

@ Zelazny7, è una buona idea, grazie.Lo aggiungerò come un'altra opzione –

+1

Per le chiamate 'unname', a patto che la si sposti in' toString' dopo, non sono necessarie come 'toString' o' paste0 (.., collapse = ",") ' non prendere i nomi comunque. – Tensibai

7

EDITS: ho accelerato codice di docendo un po 'di classificare il vettore prima, così ora sono in realtà su un piano di parità.

Ho anche aggiunto l'approccio di 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

Penso che sostituire la pasta esterna0 con toString lo renda più pulito (per lo stesso risultato), e tu non stia chiamando unname, che non ha alcun interesse in realtà quando avvolgi il risultato all'interno di una pasta0 o chiamata toString, quindi forse è da lì che proviene il guadagno. – Tensibai

+0

Nessuna modifica reale delle prestazioni, ma l'uso di 'toString' potrebbe togliere la flessibilità di scegliere il tuo carattere di collasso (ad esempio, se si desidera" 1-3; 5; 7-12 "). Quindi sembra una questione di preferenza e utilità. – Benjamin

+0

In realtà è solo che salva il collasso = "," in questo caso particolare. Ho pensato che valesse la pena :) – Tensibai

Problemi correlati