Як ефективно сортувати символи в рядку в 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)

Однак вектор рядків, до яких мені потрібно застосувати це, дуже довгий, і ця функція занадто повільна. Хтось має пропозиції щодо підвищення ефективності?


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: « попередження - це якщо рядки різної довжини, вам потрібно буде видалити будь-які порожні або 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 = "") }))
Карл Бонері

@Gregor це робить. Щойно перевірений і здається?
Карл Бонері

Класно, просто перевіряю :)
Грегор Томас

Ні, зовсім не було .. повністю у мене було те саме питання .. Що означає пропустити замітку, яку я поставив у відповідь про видалення 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
Я думаю, що це закінчиться швидше, ніж застосувати, а не покладатися на видалення порожніх значень, якщо різної довжини. Ви можете запропонувати одну петлю, загорнуту в список, але?
Карл Бонері

1
Один цикл покращує швидкість трохи більше, дякую!
Грегор Томас

людина. це все ще мене клопоче, хоча. Мені здається, що їм не вистачає дуже очевидного і простішого способу зробити все це ....
Карл Бонері

Я маю на увазі, було б, ймовірно, досить легко написати функцію RCPP, яка саме це робить, і буде блискавично. Але працюючи в R, я думаю, що ми обмежені в основному цим кроком.
Грегор Томас

ось що я думав: C ++
Carl Boneri

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

Але я думаю, що це може бути оптимізовано


Працює лише в тому випадку, якщо довжина всіх струн однакова. Хороший і швидкий, хоча!
Грегор Томас
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.