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あなたの例のように、常に長さ3のものではなく、彼らは何ですか?
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:?警告は、文字列の長さが異なる場合は、以下の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、

@Gregorします。ちょうどテストして見えた?
Carl Boneri、

かっこいい、チェック中:)
Gregor Thomas

いいえ、まったく..まったく同じ質問を自分自身で..これは、NA /空を削除することについての回答に記したメモを省略することを意味します...必要ありません。stringi私のお気に入りのパッケージは...これまでの男性である
カール・Boneri

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つのループを提案するかもしれませんが、
Carl Boneri、

1
シングルループにより、速度が少し向上します。ありがとうございます。
Gregor Thomas

うん男。しかし、これはまだ私を悩ませています。私はこの全体を行うための非常に明白で簡単な方法を見逃しているように感じます...
カールボネリ

つまり、これを行うだけで非常に高速なRCPP関数を作成するのはおそらくかなり簡単でしょう。しかし、R内での作業では、基本的にこれらの手順を実行することに限定されていると思います。
Gregor Thomas

それが私が考えていたものです。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])
}

しかし、それは最適化されていると思う


すべての文字列の長さが同じ場合にのみ機能します。しかし、素晴らしく、迅速です!
Gregor Thomas
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.