2017-02-19 10 views
3

私は、反復のたびに文字列をコピーすることなく、高速でループ内にperlの文字列を追加したいと考えています。私はJavaやC#からStringBuilderのようなものを探しています。perlで文字列をインクリメントする最速の方法は何ですか?

「a + = b」を実行するために、現在以下の選択肢を念頭に置いています。

  1. = B#1連結
  2. =参加( ''、B)。 #私は他のすべての文字列をコピーすることに興味を持っていないです
  3. プッシュ@a、B#アレイプッシュ

に参加します。私は時間ごとに1文字をコピーするか、小さな文字列をforeach反復に追加する必要があります。私は次の問題を解決しようとしています:入力文字列 'aaabbccc'を '3a2b3c'に圧縮します。したがって、入力文字列を繰り返し処理し、繰り返し文字をいくつ確認してから、圧縮された形で出力に追加することが考えられます。 perlでこれを実行するのに最も効率的なのは何ですか?

Here is a link私は解決しようとしていました。私は少し異なっています。

+2

あなたは考えがあり、 'S /([AZ])\ 1 + /($ + [0] - $ - [0])。 $ 1/eg'? – melpomene

+0

'aabbaa'をお持ちの方は' 2a2b2a'または '4a2b'に圧縮しますか? –

+0

もし私がaabbaaを持っていたら、それは2a2b2aに圧縮されるべきです。 –

答えて

4

、私は、文字列を圧縮するあなたの実際問題を解決するためのさまざまなバージョンをテストしてみました。

use strict; 
use warnings; 

use Benchmark qw(cmpthese); 
use Inline C => './compress_c.c'; 

my $str_len = 10000; 
my @chars = qw(a b c d); 
my $str; 
$str .= [@chars]->[rand 4] for 1 .. $str_len; 

cmpthese(
    -1, 
    { 
     compress_array => sub { compress_array($str) }, 
     compress_regex => sub { compress_regex($str) }, 
     compress_str => sub { compress_str($str) }, 
     compress_c  => sub { compress_c($str) }, 
    } 
); 

# Suggested by @melpomene in the comments 
sub compress_regex { 
    return $_[0] =~ s/([a-z])\1+/($+[0] - $-[0]) . $1/egr; 
} 

sub compress_array { 
    my $result = ''; 

    my @chrs = split //, $_[0]; 

    my $prev = $chrs[0]; 
    my $count = 1; 
    my @result; 
    for my $i (1..$#chrs) { 
     my $char = $chrs[$i]; 
     if ($prev eq $char) { 
      $count++; 
      next if $i < $#chrs; 
     } 
     if ($count > 1) { 
      push @result, $count, $prev; 
     } 
     else { 
      push @result, $prev; 
     } 
     if (($i == $#chrs) and ($prev ne $char)) { 
      push @result, $char; 
      last; 
     } 
     $count = 1; 
     $prev = $char; 
    } 

    return join '', @result; 
} 

sub compress_str { 
    my $result = ''; 
    my $prev = substr $_[0], 0, 1; 
    my $count = 1; 
    my $lastind = (length $_[0]) - 1; 
    for my $i (1 .. $lastind) { 
     my $char = substr $_[0], $i, 1; 
     if ($prev eq $char) { 
      $count++; 
      next if $i < $lastind; 
     } 

     if ($count > 1) { 
      $result .= $count; 
     } 
     $result .= $prev; 
     if (($i == $lastind) and ($prev ne $char)) { 
      $result .= $char; 
      last; 
     } 
     $count = 1; 
     $prev = $char; 
    } 

    return $result; 
} 

compress_c.cは次のとおりです:ここに私のテストスクリプトtest.plある

SV *compress_c(SV* str_sv) { 
    STRLEN len; 
    char* str = SvPVbyte(str_sv, len); 

    SV* result = newSV(len); 
    char *buf = SvPVX(result); 

    char prev = str[0]; 
    int count = 1; 
    int j = 0; 
    int i; 
    for (i = 1; i < len; i++) 
    { 
    char cur = str[i]; 
     if (prev == cur) { 
      count++; 
      if (i < (len - 1)) 
       continue; 
     } 

     if (count > 1) { 
      buf[j++] = count + '0'; // assume count is less than 10 
     } 

     buf[j++] = prev; 
     if ((i == (len - 1)) && (prev != cur)) buf[j++] = cur; 
     count = 1; 
     prev = cur; 
    } 

    buf[j] = '\0'; 
    SvPOK_on(result); 
    SvCUR_set(result, j); 
    return result; 
} 

perl test.plの実行結果:正規表現のバージョンは、文字列のバージョンよりも若干速いことを示している

    Rate compress_array compress_str compress_regex compress_c 
compress_array 311/s    --   -42%   -45%   -99% 
compress_str  533/s   71%   --   -6%   -98% 
compress_regex 570/s   83%   7%    --   -98% 
compress_c  30632/s   9746%   5644%   5273%   -- 

。しかし、Cのバージョンは最も速く、正規表現のバージョンの約50倍の速さです。

注:私は私のUbuntu 16.10のラップトップでこれをテストした(インテルCore i7-7500U CPUの@の2.70GHz)が

+0

@ikegami編集とコメントありがとう!私は 'malloc'呼び出しを取り除く方法を知っていたので、バッファを2回割り当てる必要はありませんでした。私は いくつかの質問を持っています:1. 'SvGETMAGIC(str_sv)'は、 'SvPVbyte(str_sv、len)'を使用しているときには必要ですか? ['perlapi'](http://perldoc.perl.org/perlapi.html)' SvPV() 'ハンドルは の魔法を得ます。 2.なぜ 'SvGETMAGIC()'の後ろにブレースされたブロックがありますか? ここにローカルスコープを追加する理由が見つかりません。 3.「削除した Unicodeバグ」*(回答の改訂履歴のコメント)はどういう意味ですか? –

+0

... 4.またコメント* "コンパイルエラー(' for'の中の 'int')" *を修正しました。 C99の 'for'ループ仕様で変数を宣言することは有効です([this answer](http://stackoverflow.com/a/1287867/2173773)参照)。 –

+1

私は解決しようとしていた元の問題のベンチマークを[ここでコミットしました](https://github.com/andrepontesmelo/CtCI-6th-Edition-Perl/tree/master/Chapter1/6_String_Compression)でした。それはやや異なる問題です。私は同じ結果を持っている。 –

2

私はそれを実行するには、いくつかの方法で、以下のベンチマークを実行した:

#!/usr/bin/perl 
use strict; 
use warnings; 
use Benchmark qw(cmpthese); 

my $dna; 
$dna .= [qw(G A T C)]->[rand 4] for 1 .. 10000; 

sub frequency_concat { 
    my $result = ''; 

    for my $idx (0 .. length($dna) - 1) { 
      $result .= substr($dna, $idx, 1); 
    } 

    return $result; 
} 

sub frequency_join { 
    my $result = ''; 

    for my $idx (0 .. length($dna) - 1) { 
      $result = join '', $result, substr($dna,$idx,1); 
    } 

    return $result; 
} 

sub frequency_list_push { 
     my @result =(); 

     for my $idx (0 .. length($dna) - 1) { 
       push @result, substr($dna,$idx,1); 
     } 

     return join '', @result; 
} 

sub frequency_list_prealloc { 
      my @result = (' ' x length($dna)); 

      for my $idx (0 .. length($dna) - 1) { 
        $result[$idx] = substr($dna,$idx,1); 
      } 

      return join '', @result; 
} 


cmpthese(-1, # Run each for at least 1 second(s) { 
       concat => \&frequency_concat, 
       join => \&frequency_join, 
       list_push => \&frequency_list_push, 
       list_list_prealloc => \&frequency_list_prealloc 
     } 
    ); 

以下の結果は、CONCAT(b)は、最速の動作であることが示されています。なぜか、これは文字列のいくつかのコピーを作る必要があるので、私は理解していません。 comparsionについては

    Rate   join list_push list_list_prealloc   concat 
join    213/s   --  -38%    -41%  -74% 
list_push   342/s   60%   --    -5%  -58% 
list_list_prealloc 359/s   68%   5%     --  -56% 
concat    822/s   285%  140%    129%   -- 
+3

"list prealloc"の場合、配列はあらかじめ割り当てられていません。それは、長い文字列である単一の要素を割り当てます。 – melpomene

+1

IMHOは、文字列内の文字の出現回数を数えるなど、**全体の**問題をよりよくベンチマークします。 – jm666

+0

これを私のUbuntu 16でテストしました。10ラップトップ(Intel Core i7-7500U CPU @ 2.70GHz)と同様の結果を得ました。私は 'substr'への呼び出しを取り除き、定数1の文字列で置き換えました。今、「コンカット」は3229 /秒で最も速かった。私はまた、事前に割り当てられたCバージョンを実装しました(['Inline :: C'](https://metacpan.org/pod/distribution/Inline-C/lib/Inline/C.pod)を使って)、約90倍速くなりました'concat'バージョン(284350/s)よりも優れています。私が 'substr'呼び出しを削除した理由は、CバージョンとPerlバージョンをより簡単に比較できるようにするためでした。 –

関連する問題