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 패키지를 확인하십시오-속도 향상 대 기본 기능을 제공합니다. Rich Scriven의 답변에 대한 자세한 내용은 stackoverflow.com/questions/5904797/…
user2474226

letters당신의 예에서와 같이 항상 길이 세의하지, 그들은입니까?
jay.sf 2009 년

아니요, 줄 길이는 다를 수 있습니다.
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) 결합

여러 루프와 나열 해제를 피하십시오 .... 무시 : ? caveat는 문자열의 길이가 다른 경우 apply와 같은 빈 또는 NA를 제거해야합니다.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 초로 이동


원래 기능을 병렬로 실행하면 속도가 어떻게됩니까?
slava-kohut

50 %가 약간 줄었습니다. tmp <- strsplit(strings, split="") unlist(mclapply(tmp, function(i){ paste0(sort(i), collapse = "") }))
Carl Boneri

@ 그레고르. 방금 테스트를 거친 것으로 보입니까?
Carl Boneri

쿨, 단지 :) 확인
그레고르 토마스

전혀 그렇지 않습니다. 완전히 같은 질문을했습니다.. 이것은 NA / 빈을 제거하는 것에 대한 답변에 적어 놓은 메모를 생략한다는 것을 의미합니다. 필요하지 않습니다. stringi먼 사람에 의해 내가 가장 좋아하는 패키지입니다 ...
Carl Boneri

4

를 사용하여 다시 구현 stringi하면 약 4 배의 속도가 향상됩니다. 또한 편집 sort_cat사용 fixed = TRUEstrsplit을하게하는, 조금 더 빨리. 그리고 단일 루프 제안을 해준 Carl 덕분에 조금 더 빨라졌습니다.

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
나는 이것이 적용보다 빨리 끝나고 길이가 다른 경우 빈 값을 제거하는 것에 의존하지 않는다고 생각합니다. 그래도 하나의 루프가 목록에 싸여 있다고 제안 할 수 있습니까?
Carl Boneri

1
싱글 루프는 속도를 조금 더 향상시킵니다.
Gregor Thomas

맞아 그래도 여전히 나를 괴롭 히고 있습니다. 나는 .... 메신저가이 모든 일을 할 수있는 매우 분명하고 쉬운 방법 누락 같은 느낌
칼 Boneri

내 말은 아마도 RCPP 함수를 작성하는 것이 매우 쉽고 아마도 번개처럼 빠를 것입니다. 그러나 R 내에서 일하면서 기본적으로 이러한 단계를 수행하는 것으로 제한됩니다.
Gregor Thomas

그것이 내가 생각했던 것 : 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])
}

하지만 최적화 된 것 같아요


모든 문자열의 길이가 동일한 경우에만 작동합니다. 그래도 좋고 빠르다!
Gregor Thomas
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.