¿Cómo ordenar eficientemente los caracteres en una cadena en R?


9

¿Cómo puedo ordenar eficientemente los caracteres de cada cadena en un vector? Por ejemplo, dado un vector de cadenas:

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

He escrito una función que dividirá cada cadena en un vector, clasificará el vector y luego colapsará la salida:

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)

Sin embargo, el vector de cadenas al que necesito aplicar esto es muy largo y esta función es demasiado lenta. ¿Alguien tiene alguna sugerencia sobre cómo mejorar el rendimiento?


1
Echa un vistazo al paquete stringi: ofrece una aceleración vs base. La respuesta de Rich Scriven da más detalles: stackoverflow.com/questions/5904797/…
user2474226

El lettersno siempre son de la longitud de tres como en su ejemplo, ¿verdad?
jay.sf

No, la longitud de las cuerdas puede variar.
Powege

Creo que la adición fixed = TRUEde strsplit()puede mejorar el rendimiento, ya que no implica el uso de expresiones regulares.
tmfmnk

Respuestas:


3

Puede reducir el tiempo minimizando la cantidad de bucles con seguridad, y hacerlo aún más utilizando el parallelpaquete ... mi enfoque sería dividir cadenas una vez, luego en el bucle ordenar y pegar:

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

Se afeita como 4 segundos, pero aún no es tan rápido ...

Editar

Bien, lo conseguí usando la applyestrategia aquí:

1) extraer letras en lugar de dividir límites 2) crear una matriz con los resultados 3) iterar por filas 4) Ordenar 5) Unir

A evitar múltiples bucles y unlisting .... IGNORE: ? Salvedad es si cadenas diferentes longitudes, tendrá que eliminar cualquier vacío o NA dentro de la applytales comoi[!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

Nos lleva de 10.3 segundos a 3.98


¿Cuál es la aceleración si ejecuta la función original en paralelo?
slava-kohut

derribado por un poco más del 50%. tmp <- strsplit(strings, split="") unlist(mclapply(tmp, function(i){ paste0(sort(i), collapse = "") }))
Carl Boneri

@ Gregor lo hace. Acabo de probar y parece?
Carl Boneri

Genial, solo comprobando :)
Gregor Thomas

No, en absoluto ... totalmente tuve la misma pregunta ... lo que significa omitir la nota que puse en la respuesta sobre la eliminación de NA / vacío ... no lo necesito. stringies mi paquete favorito de lejos hombre ...
Carl Boneri

4

La reimplementación usando stringiproporciona una aceleración de aproximadamente 4x. También edité sort_catpara usar fixed = TRUEen el strsplit, lo que lo hace un poco más rápido. Y gracias a Carl por la sugerencia de bucle único, que nos acelera un poco más.

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>

Este método también podría usarse en paralelo. Perfilar el código para ver qué operaciones realmente toman más tiempo sería un buen próximo paso si desea ir aún más rápido.


1
Creo que esto terminará más rápido que aplicar y no dependerá de eliminar valores vacíos si tienen longitudes diferentes. ¿podría sugerir un bucle envuelto en unlist?
Carl Boneri

1
Single loop mejora la velocidad un poco más, ¡gracias!
Gregor Thomas el

si hombre. Sin embargo, esto todavía me está molestando. Siento que me estoy perdiendo una manera muy obvia y más fácil de hacer todo esto ...
Carl Boneri

Quiero decir, probablemente sería bastante fácil escribir una función RCPP que simplemente haga esto y sea increíblemente rápido. Pero trabajando dentro de R, creo que estamos limitados a hacer básicamente estos pasos.
Gregor Thomas

eso es lo que estaba pensando: C ++
Carl Boneri

1

Esta versión es un poco más rápida.

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

Pero creo que podría estar optimizado.


Solo funcionará si la longitud de todas las cadenas es la misma. ¡Agradable y rápido, sin embargo!
Gregor Thomas
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.