Come ordinare in modo efficiente i caratteri in una stringa in R?


9

Come posso ordinare in modo efficiente i caratteri di ogni stringa in un vettore? Ad esempio, dato un vettore di stringhe:

set.seed(1)
strings <- c(do.call(paste0, replicate(4, sample(LETTERS, 10000, TRUE), FALSE)),
do.call(paste0, replicate(3, sample(LETTERS, 10000, TRUE), FALSE)),
do.call(paste0, replicate(2, sample(LETTERS, 10000, TRUE), FALSE)))

Ho scritto una funzione che divide ogni stringa in un vettore, ordina il vettore e quindi comprime l'output:

sort_cat <- function(strings){
  tmp <- strsplit(strings, split="")
  tmp <- lapply(tmp, sort)
  tmp <- lapply(tmp, paste0, collapse = "")
  tmp <- unlist(tmp)
  return(tmp)
}
sorted_strings <- sort_cat(strings)

Tuttavia, il vettore di stringhe a cui devo applicare questo è molto lungo e questa funzione è troppo lenta. Qualcuno ha qualche suggerimento su come migliorare le prestazioni?


1
Dai un'occhiata al pacchetto stringi - offre uno speedup vs base. La risposta di Rich Scriven fornisce ulteriori dettagli: stackoverflow.com/questions/5904797/...
user2474226

Non letterssono sempre di lunghezza tre come nel tuo esempio, vero?
jay.sf

No, la lunghezza delle stringhe può variare.
Powege

Credo che l'aggiunta fixed = TRUEnel strsplit()possa migliorare le prestazioni in quanto non comportano l'uso di espressioni regolari.
tmfmnk,

Risposte:


3

Puoi ridurre il tempo minimizzando sicuramente il numero di loop e farlo ulteriormente usando il parallelpacchetto ... Il mio approccio sarebbe quello di dividere le stringhe una volta, quindi nell'ordinamento del loop e incollare:

sort_cat <- function(strings){
    tmp <- strsplit(strings, split="")
    tmp <- lapply(tmp, sort)
    tmp <- lapply(tmp, paste0, collapse = "")
    tmp <- unlist(tmp)
    return(tmp)
}

sort_cat2 <- function(strings){
    unlist(mcMap(function(i){
        stri_join(sort(i), collapse = "")
    }, stri_split_regex(strings, "|", omit_empty = TRUE, simplify = F), mc.cores = 8L))
}

> microbenchmark::microbenchmark(
+     old = sort_cat(strings[1:500000]),
+     new = sort_cat2(strings[1:500000]),
+     times = 1
+ )
Unit: seconds
 expr        min         lq       mean     median         uq        max neval
  old 9.62673395 9.62673395 9.62673395 9.62673395 9.62673395 9.62673395     1
  new 5.10547437 5.10547437 5.10547437 5.10547437 5.10547437 5.10547437     1

Rade come 4 secondi, ma non è ancora così veloce ...

modificare

Va bene, usando la applystrategia qui:

1) estrarre lettere anziché dividere i confini 2) creare una matrice con i risultati 3) scorrere tra le righe 4) ordinare 5) unire

Si evitano più loop e non in elenco .... IGNORA:? Avvertenza se stringhe di lunghezze diverse, sarà necessario rimuovere qualsiasi vuoto o NA all'interno applycomei[!is.na(i) && nchar(i) > 0]

sort_cat3 <- function(strings){
    apply(stri_extract_all_regex(strings, "\\p{L}", simplify = TRUE), 1, function(i){
        stri_join(stri_sort(i), collapse = "")
    })
}

> microbenchmark::microbenchmark(
+     old = sort_cat(strings[1:500000]),
+     mapping = sort_cat2(strings[1:500000]),
+     applying = sort_cat3(strings[1:500000]),
+     times = 1
+ )
Unit: seconds
     expr         min          lq        mean      median          uq         max neval
      old 10.35101934 10.35101934 10.35101934 10.35101934 10.35101934 10.35101934     1
  mapping  5.12771799  5.12771799  5.12771799  5.12771799  5.12771799  5.12771799     1
 applying  3.97775326  3.97775326  3.97775326  3.97775326  3.97775326  3.97775326     1

Ci porta da 10,3 secondi a 3,98


Qual è lo speedup se si esegue la funzione originale in parallelo?
slava-kohut,

abbattuto di poco più del 50%. tmp <- strsplit(strings, split="") unlist(mclapply(tmp, function(i){ paste0(sort(i), collapse = "") }))
Carl Boneri,

@Gregor lo fa. Appena testato e sembra?
Carl Boneri,

Bene, sto solo controllando :)
Gregor Thomas,

No per niente .. ho avuto la stessa domanda da solo .. il che significa che ometti la nota che ho inserito nella risposta relativa alla rimozione di NA / vuoto ... non ne ho bisogno. stringiè il mio pacchetto preferito dall'uomo lontano ...
Carl Boneri,

4

La reimplementazione dell'uso stringidà un aumento di circa 4x. Ho anche modificato sort_catper usarlo fixed = TRUEin strsplit, il che lo rende un po ' più veloce. E grazie a Carl per il suggerimento single loop, che ci velocizza un po 'di più.

sort_cat <- function(strings){
  tmp <- strsplit(strings, split="", fixed = TRUE)
  tmp <- lapply(tmp, sort)
  tmp <- lapply(tmp, paste0, collapse = "")
  tmp <- unlist(tmp)
  return(tmp)
}

library(stringi)
sort_stringi = function(s) {
  s = stri_split_boundaries(s, type = "character")
  s = lapply(s, stri_sort)
  s = lapply(s, stri_join, collapse = "")
  unlist(s)
}

sort_stringi_loop = function(s) {
  s = stri_split_boundaries(s, type = "character")
  for (i in seq_along(s)) {
    s[[i]] = stri_join(stri_sort(s[[i]]), collapse = "")
  }
  unlist(s)
}

bench::mark(
  sort_cat(strings),
  sort_stringi(strings),
  sort_stringi_loop(strings)
)
# # A tibble: 3 x 13
#   expression                    min median `itr/sec` mem_alloc `gc/sec` n_itr  n_gc total_time result memory
#   <bch:expr>                 <bch:> <bch:>     <dbl> <bch:byt>    <dbl> <int> <dbl>   <bch:tm> <list> <list>
# 1 sort_cat(strings)          23.01s 23.01s    0.0435    31.2MB     2.17     1    50     23.01s <chr ~ <Rpro~
# 2 sort_stringi(strings)       6.16s  6.16s    0.162     30.5MB     2.11     1    13      6.16s <chr ~ <Rpro~
# 3 sort_stringi_loop(strings)  5.75s  5.75s    0.174     15.3MB     1.74     1    10      5.75s <chr ~ <Rpro~
# # ... with 2 more variables: time <list>, gc <list>

Questo metodo potrebbe anche essere usato in parallelo. Profilare il codice per vedere quali operazioni effettivamente impiegano più tempo sarebbe un buon passo successivo se vuoi andare ancora più veloce.


1
Penso che questo finirà più velocemente di quanto si applichi e non fare affidamento sulla rimozione di valori vuoti se lunghezze diverse. potresti suggerire un loop racchiuso in unlist, però?
Carl Boneri,

1
Il single loop migliora la velocità solo un po 'di più, grazie!
Gregor Thomas,

sì amico. questo mi sta ancora infastidendo. Mi sento come se mi mancasse un modo molto ovvio e più semplice per fare tutto questo ...
Carl Boneri,

Voglio dire, sarebbe probabilmente abbastanza facile scrivere una funzione RCPP che fa proprio questo e sarebbe velocissima. Ma lavorando all'interno di R, penso che siamo limitati a fare sostanzialmente questi passaggi.
Gregor Thomas,

questo è quello che stavo pensando: C ++
Carl Boneri,

1

Questa versione è leggermente più veloce

sort_cat2=function(strings){
A=matrix(unlist(strsplit(strings,split="")),ncol=3,byrow=TRUE)
B=t(apply(A,1,sort))
paste0(B[,1],B[,2],B[,3])
}

Ma penso che potrebbe essere ottimizzato


Funzionerà solo se la lunghezza di tutte le stringhe è la stessa. Bello e veloce, però!
Gregor Thomas,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.