Como classificar eficientemente os caracteres em uma string em R?


9

Como posso classificar eficientemente os caracteres de cada string em um vetor? Por exemplo, dado um vetor de strings:

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

Eu escrevi uma função que irá dividir cada seqüência de caracteres em um vetor, classificar o vetor e recolher a saída:

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)

No entanto, o vetor de strings ao qual preciso aplicar isso é muito longo e essa função é muito lenta. Alguém tem alguma sugestão de como melhorar o desempenho?


11
Confira o pacote stringi - ele oferece uma aceleração versus base. A resposta de Rich Scriven fornece mais detalhes: stackoverflow.com/questions/5904797/… #
user2474226:

O lettersnão são sempre de comprimento três como no seu exemplo, são eles?
Java.sf # 8/19

Não, o comprimento das strings pode variar.
Powege 8/08/19

Eu acho que a adição fixed = TRUEde strsplit()pode melhorar o desempenho, pois não irá envolver o uso de regex.
tmfmnk

Respostas:


3

Você pode reduzir o tempo minimizando o número de loops, com certeza, e ainda usando o parallelpacote ... minha abordagem seria dividir seqüências de caracteres uma vez e, em seguida, classificar e colar o loop:

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

Raspa-se como 4 segundos, mas ainda não é tão rápido ...

Editar

Ok, consegui resolver o problema usando apply.. estratégia aqui:

1) extrair letras em vez de dividir limites 2) criar uma matriz com os resultados 3) iterar por linhas 4) Classificar 5) Unir

Você evita vários loops e cancelamentos de lista. ... IGNORE:? Ressalva é que, se as cordas tiverem comprimentos diferentes, será necessário remover qualquer NA vazia ou NA apply, 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

Leva-nos de 10,3 segundos para 3,98


Qual é a aceleração se você executar a função original em paralelo?
slava-kohut

reduzido em pouco mais de 50%. tmp <- strsplit(strings, split="") unlist(mclapply(tmp, function(i){ paste0(sort(i), collapse = "") }))
Carl Boneri 8/10/19

@Gregor faz. Apenas testado e parece?
24519 Carl Boneri

Cool, apenas verificando :)
Gregor Thomas

Não, de maneira alguma .. totalmente eu mesma tive a mesma pergunta .. o que significa omitir a nota que coloquei na resposta sobre a remoção de NA / vazia ... não precisa dela. stringié meu pacote favorito pelo homem agora ...
Carl Boneri

4

A reimplementação usando stringifornece uma aceleração de aproximadamente 4x. Também editei sort_catpara usar fixed = TRUEno strsplit, o que o torna um pouco mais rápido. E obrigado a Carl pela sugestão de loop único, que nos acelera um pouco mais.

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 também pode ser usado em paralelo. Criar um perfil do código para ver quais operações realmente demoram mais seria um bom próximo passo, se você quiser ir ainda mais rápido.


11
Eu acho que isso vai acabar mais rápido do que se aplica e não depende da remoção de valores vazios, se diferentes comprimentos. pode sugerir um loop envolto em unlist, embora?
Carl Boneri 8/10/19

11
O loop único melhora a velocidade um pouco mais, obrigado!
Gregor Thomas

sim cara. isso ainda está me incomodando, no entanto. Eu me sinto como im faltando uma maneira muito óbvia e mais fácil de fazer essa coisa toda ....
Carl Boneri

Quero dizer, provavelmente seria muito fácil escrever uma função RCPP que apenas faça isso e seria extremamente rápida. Mas trabalhando no R, acho que estamos limitados a basicamente executar essas etapas.
Gregor Thomas

Isso é o que eu estava pensando: C ++
Carl Boneri

1

Esta versão é um pouco mais 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])
}

Mas acho que pode ser otimizado


Só funcionará se o comprimento de todas as strings for o mesmo. Bom e rápido, no entanto!
Gregor Thomas
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.