Làm thế nào để sắp xếp hiệu quả các ký tự trong một chuỗi trong R?


9

Làm thế nào tôi có thể sắp xếp hiệu quả các ký tự của mỗi chuỗi trong một vectơ? Ví dụ, đưa ra một vectơ của chuỗi:

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

Tôi đã viết một hàm sẽ chia mỗi chuỗi thành một vectơ, sắp xếp vectơ và sau đó thu gọn đầu ra:

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)

Tuy nhiên, vectơ của chuỗi tôi cần áp dụng điều này rất dài và chức năng này quá chậm. Có ai có bất kỳ đề nghị cho làm thế nào để cải thiện hiệu suất?


1
Kiểm tra gói Stringi - nó cung cấp một tốc độ so với cơ sở. Câu trả lời của Rich Scriven cung cấp thêm chi tiết: stackoverflow.com/questions/5904797/
triệt

Các letterskhông phải lúc nào chiều dài ba như trong ví dụ của bạn, chúng là gì?
jay.sf

Không, độ dài của chuỗi có thể thay đổi.
Powege

Tôi nghĩ rằng việc thêm fixed = TRUEvào strsplit()có thể cải thiện hiệu suất vì nó sẽ không liên quan đến việc sử dụng regex.
tmfmnk

Câu trả lời:


3

Bạn có thể giảm thời gian bằng cách giảm thiểu số lượng vòng lặp cho chắc chắn, và làm như vậy bằng cách sử dụng parallelgói ... cách tiếp cận của tôi sẽ được chia chuỗi một lần, sau đó trong sắp xếp vòng lặp và dán:

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

Rung như 4 giây, nhưng nó vẫn không nhanh như vậy ...

Biên tập

Được rồi, sử dụng apply.. chiến lược ở đây:

1) trích xuất các chữ cái thay vì phân chia ranh giới 2) tạo ma trận với kết quả 3) lặp qua hàng thông minh 4) Sắp xếp 5) Tham gia

Bạn tránh nhiều vòng lặp và không niêm yết .... IGNORE : ? Hãy cẩn thận nếu các chuỗi có độ dài khác nhau, bạn sẽ cần xóa bất kỳ khoảng trống hoặc NA nào trong phạm vi applynhư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

Đưa chúng tôi từ 10,3 giây đến 3,98


Tăng tốc nếu bạn chạy song song chức năng ban đầu là gì?
slava-kohut

giảm hơn 50% một chút. tmp <- strsplit(strings, split="") unlist(mclapply(tmp, function(i){ paste0(sort(i), collapse = "") }))
Carl Boneri

@Gregor nó làm. Chỉ cần thử nghiệm và xuất hiện?
Carl Boneri

Thật tuyệt, chỉ cần kiểm tra :)
Gregor Thomas

Không hoàn toàn không .. bản thân tôi cũng có cùng một câu hỏi .. điều đó có nghĩa là bỏ qua ghi chú tôi đưa vào câu trả lời liên quan đến việc xóa NA / trống ... không cần nó. stringilà gói yêu thích của tôi bởi người đàn ông xa ...
Carl Boneri

4

Việc thực hiện lại bằng cách sử dụng stringicho tốc độ tăng gấp 4 lần. Tôi cũng thay đổi nội dung sort_catđể sử dụng fixed = TRUEtrong strsplit, mà làm cho nó một chút nhanh hơn. Và cảm ơn Carl vì gợi ý vòng lặp đơn, giúp chúng tôi tăng thêm một chút nữa.

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>

Phương pháp này cũng có thể được sử dụng song song. Cấu hình mã để xem hoạt động nào thực sự mất nhiều thời gian nhất sẽ là bước tiếp theo tốt nếu bạn muốn đi nhanh hơn nữa.


1
Tôi nghĩ rằng điều này sẽ kết thúc nhanh hơn áp dụng và không dựa vào việc loại bỏ các giá trị trống nếu độ dài khác nhau. có thể đề nghị một vòng lặp được bọc trong danh sách, mặc dù?
Carl Boneri

1
Vòng lặp đơn cải thiện tốc độ chỉ một chút nữa, cảm ơn!
Gregor Thomas

phải điều này vẫn còn làm phiền tôi Tôi cảm thấy như tôi đang thiếu một cách rất rõ ràng và dễ dàng hơn để làm toàn bộ điều này ....
Carl Boneri

Ý tôi là, có lẽ sẽ khá dễ dàng để viết một hàm RCPP chỉ cần làm điều này và sẽ nhanh như chớp. Nhưng làm việc trong R, tôi nghĩ rằng về cơ bản chúng tôi giới hạn thực hiện các bước này.
Gregor Thomas

đó là những gì tôi đã nghĩ: C ++
Carl Boneri

1

Phiên bản này nhanh hơn một chút

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

Nhưng tôi nghĩ nó có thể được tối ưu hóa


Sẽ chỉ hoạt động nếu độ dài của tất cả các chuỗi là như nhau. Đẹp và nhanh chóng, mặc dù!
Gregor Thomas
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.