Bagaimana cara mengurutkan karakter dalam string dalam R secara efisien?

9

Bagaimana saya bisa mengurutkan karakter setiap string secara efisien dalam vektor? Misalnya, diberi vektor string:

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

Saya telah menulis fungsi yang akan membagi setiap string menjadi vektor, mengurutkan vektor, dan kemudian menciutkan hasilnya:

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)

Namun, vektor string yang saya perlu terapkan ini sangat panjang, dan fungsi ini terlalu lambat. Adakah yang punya saran untuk meningkatkan kinerja?

Powege
sumber
1
Lihatlah paket stringi - ia menawarkan speedup vs base. Jawaban Rich Scriven memberikan perincian lebih lanjut: stackoverflow.com/questions/5904797/...
user2474226
The letterstidak selalu panjang tiga seperti dalam contoh Anda, apakah mereka?
jay.sf
Tidak, panjang string dapat bervariasi.
Powege
Saya berpikir bahwa menambahkan fixed = TRUEdalam strsplit()dapat meningkatkan kinerja karena tidak akan melibatkan penggunaan regex.
tmfmnk

Jawaban:

3

Anda dapat mengurangi waktu dengan meminimalkan jumlah loop pasti, dan selanjutnya melakukannya dengan menggunakan parallelpaket ... pendekatan saya akan membagi string sekali, kemudian di pengurutan lingkaran dan tempel:

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

Bercukur seperti 4 detik, tapi masih tidak secepat itu ...

Edit

Oke turun menggunakan apply.. strategi di sini:

1) mengekstrak surat daripada membagi batas 2) membuat matriks dengan hasil 3) beralih melalui baris-bijaksana 4) Urutkan 5) Bergabunglah

Anda menghindari beberapa loop dan tidak mencantumkan .... IGNORE:? Peringatan adalah jika string memiliki panjang yang berbeda, Anda harus menghapus semua kosong atau NA dalam applysepertii[!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

Membawa kami dari 10,3 detik menjadi 3,98

Carl Boneri
sumber
Apa speedup jika Anda menjalankan fungsi asli secara paralel?
slava-kohut
dijatuhkan oleh sedikit di atas 50%. tmp <- strsplit(strings, split="") unlist(mclapply(tmp, function(i){ paste0(sort(i), collapse = "") }))
Carl Boneri
@ Kebaikan itu. Baru diuji dan tampaknya?
Carl Boneri
Keren, baru saja mengecek :)
Gregor Thomas
Tidak tidak sama sekali .. saya sendiri benar-benar memiliki pertanyaan yang sama .. yang berarti menghilangkan catatan yang saya masukkan dalam jawaban tentang menghapus NA / kosong ... tidak membutuhkannya. stringiadalah paket favorit saya sejauh ini ...
Carl Boneri
4

Mengimplementasikan kembali menggunakan stringimemberi sekitar 4x speedup. Saya juga mengedit sort_catuntuk digunakan fixed = TRUEdalam strsplit, yang membuatnya sedikit lebih cepat. Dan terima kasih kepada Carl untuk saran satu putaran, yang mempercepat kita sedikit lebih banyak.

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>

Metode ini juga bisa digunakan secara paralel. Membuat profil kode untuk melihat operasi mana yang benar-benar memakan waktu paling lama akan menjadi langkah selanjutnya yang baik jika Anda ingin lebih cepat lagi.

Gregor Thomas
sumber
1
Saya pikir ini akan berakhir lebih cepat daripada yang berlaku dan tidak bergantung pada menghapus nilai kosong jika panjangnya berbeda. mungkin menyarankan satu loop yang terbungkus tidak terdaftar?
Carl Boneri
1
Single loop meningkatkan kecepatan hanya sedikit lagi, terima kasih!
Gregor Thomas
ya laki-laki ini masih menggangguku. Saya merasa saya kehilangan cara yang sangat jelas dan mudah untuk melakukan semua ini ....
Carl Boneri
Maksudku, mungkin akan cukup mudah untuk menulis fungsi RCPP yang hanya melakukan ini dan akan secepat kilat. Tetapi bekerja di dalam R, saya pikir kita pada dasarnya hanya melakukan langkah-langkah ini.
Gregor Thomas
itulah yang saya pikirkan: C ++
Carl Boneri
1

Versi ini sedikit lebih cepat

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

Tapi saya pikir itu mungkin dioptimalkan

Félix Cuneo
sumber
Hanya akan berfungsi jika panjang semua senarnya sama. Bagus dan cepat!
Gregor Thomas