Как эффективно отсортировать символы в строке в R?

9

Как я могу эффективно отсортировать символы каждой строки в векторе? Например, задан вектор строк:

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

Я написал функцию, которая будет разбивать каждую строку на вектор, сортировать вектор, а затем свернуть вывод:

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)

Однако вектор строк, к которым мне нужно применить это, очень длинный, а эта функция слишком медленная. У кого-нибудь есть предложения по улучшению производительности?

Powege
источник
1
Проверьте пакет stringi - он предлагает ускорение против базы. Ответ Рича Скривена дает более подробную информацию: stackoverflow.com/questions/5904797/…
user2474226
Они lettersне всегда имеют длину три, как в вашем примере, не так ли?
Jay.sf
Нет, длина строк может отличаться.
Powege
Я думаю , что добавление fixed = TRUEв strsplit()может улучшить производительность , поскольку она не будет включать в себя использование регулярных выражений.
tmfmnk

Ответы:

3

Вы можете сократить время, минимизировав количество циклов, а затем сделайте это с помощью parallelпакета ... мой подход будет разбивать строки один раз, затем в цикле сортировать и вставлять:

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

Бреется как 4 секунды, но все еще не так быстро ...

редактировать

Хорошо, добился успеха, используя apply.. стратегию здесь:

1) извлекать буквы, а не разбивать границы 2) создавать матрицу с результатами 3) перебирать по строкам 4) сортировать 5) объединять

Вы избегаете многократных циклов и списков ... IGNORE:? Caveat - если строки различной длины, вам нужно будет удалить все пустые или NA внутри applyтаких, какi[!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

Занимает у нас от 10,3 с до 3,98

Карл Бонери
источник
Каково ускорение, если вы запускаете исходную функцию параллельно?
Слава-Кохут
сбит чуть более 50%. tmp <- strsplit(strings, split="") unlist(mclapply(tmp, function(i){ paste0(sort(i), collapse = "") }))
Карл Бонери
@ Грегор это делает. Только что проверил и кажется?
Карл Бонери
Круто, просто проверяю :)
Грегор Томас
Нет, совсем нет ... у меня тоже был тот же вопрос ... что означает пропустить записку, которую я вставил в ответ относительно удаления NA / пусто ... мне это не нужно. stringiмой любимый пакет, безусловно, человек ...
Карл Бонери
4

Повторная реализация с использованием stringiдает примерно 4-кратное ускорение. Я также отредактировал sort_catдля использования fixed = TRUEв strsplit, что делает его немного быстрее. И спасибо Карлу за предложение по единой петле, которое ускоряет нас немного больше.

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>

Этот метод также может быть использован параллельно. Профилирование кода, чтобы увидеть, какие операции на самом деле занимают больше всего времени, было бы хорошим следующим шагом, если вы хотите пойти еще быстрее.

Грегор Томас
источник
1
Я думаю, что это закончится быстрее, чем применить, и не полагаться на удаление пустых значений, если различной длины. может предложить одну петлю в unlist?
Карл Бонери
1
Одиночная петля улучшает скорость, спасибо!
Грегор Томас
да, чувак это все еще беспокоит меня, хотя. Я чувствую, что мне не хватает очень очевидного и более простого способа сделать все это ....
Карл Бонери
Я имею в виду, что было бы довольно легко написать функцию RCPP, которая просто делает это и будет молниеносной. Но работая в R, я думаю, что мы ограничены в основном выполнением этих шагов.
Грегор Томас
это то, что я думал: C ++
Карл Бонери
1

Эта версия немного быстрее

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

Но я думаю, что это может быть оптимизировано

Феликс Кунео
источник
Будет работать, только если длина всех строк одинакова. Хотя приятно и быстро!
Грегор Томас