1

2 つの文字列を比較しようとしていますが、出力として、連続する同一文字の数が必要です。文字が異なる場合は、2 番目の文字列の char だけです。再帰的な実装が機能していますが、連続したカウントを加算する方法がわかりません

コード:

use strict;
use warnings;
use Data::Dumper;
$Data::Dumper::Indent = 0;
$Data::Dumper::Terse  = 1;

my $str1 = "aaaaaaaaaaaabbbbbbbbbbbccccccccdddddddddddeeeefffffff";
my $str2 = "aaaaaaaaaaaabbbbbbbbbbbccccccccxxxxxxxddxxeeeefffffff";

sub find_diff {
    my ( $a, $b ) = @_;
    my @rtn = ();
    my $len = length $a;
    my $div = $len / 2;
    if ( $div < 1 ) {
        return $b;
    }
    my $a_1 = substr $a, 0, $div;
    my $b_1 = substr $b, 0, $div;
    if ($a_1 eq $b_1) {
         push @rtn, length $a_1;
    }
    else {
        push @rtn, find_diff( $a_1, $b_1 );
    }
    my $a_2 = substr $a, $div;
    my $b_2 = substr $b, $div;
    if ($a_2 eq $b_2) {
        push @rtn, length $a_2;
    }
    else {
        push @rtn, find_diff( $a_2, $b_2 );
    }
    return @rtn;
}

print Data::Dumper::Dumper( [ find_diff('xaabbb', 'aaabbc' ) ] ) . "\n";
print Data::Dumper::Dumper( [ find_diff('aaabbb', 'aaabbc' ) ] ) . "\n";
print Data::Dumper::Dumper( [ find_diff( $str1, $str2 ) ] ) . "\n";

出力:

['a',2,1,1,'c']
[3,1,1,'c']
[26,3,1,1,'x','x','x','x','x','x','x',1,1,'x','x',4,7]

望ましい出力:

['a',4,'c']
[5,'c']
[31,'x','x','x','x','x','x','x',2,'x','x',11]

もちろん、文字を配列に分割して、unpack連続した一致をかなり簡単にカウントできますが、パフォーマンスを比較できるように分割統治法を試してみたいと思います。

ありがとう!

編集 - ネストされた配列を返してから減らすことで、再帰的なケースでそれを解決することができました。驚くほど遅くはありません。

sub find_diff {
    my ( $a, $b ) = @_;
    my @rtn = ();
    my $len = length $a;
    if ( $len < 2 ) {
        return [$b, 0];
    }
    my $div = $len / 2;
    my $a_1 = substr $a, 0, $div;
    my $b_1 = substr $b, 0, $div;
    if ($a_1 eq $b_1) {
        push @rtn, [length $a_1, 1];
    }
    else {
        push @rtn, find_diff( $a_1, $b_1 );
    }
    my $a_2 = substr $a, $div;
    my $b_2 = substr $b, $div;
    if ($a_2 eq $b_2) {
        push @rtn, [length $a_2, 1];
    }
    else {
        push @rtn, find_diff( $a_2, $b_2 );
    }
    return @rtn;
}
sub compress_string {
    my ($a, $b) = @_;
    my @list = find_diff($a, $b);
    my $acc = 0;
    my @result = ();
    foreach my $item (@list) {
        if ( $item->[1] ) {
            $acc += $item->[0];
        } else {
            push @result, if $acc;
            push @result, $item->[0];
            $acc = 0;
        }
    }
    push @result, $acc if $acc;
    return @result;
}

結果は私が望むものと一致します。

更新 - パフォーマンス統計

これは本当に興味深いです。使用unpack( 'C*', $string)は非常に高速であり、それが私の反復バージョンが非常に高速である理由だと思います。再帰の速度の利点は、より長い文字列 (434 文字) で出てきます。

                         Rate short_recurse_borodin short_recurse short_array_borodin short_array_sodved short_array
short_recurse_borodin  6944/s                    --          -31%                -36%               -73%        -84%
short_recurse         10091/s                   45%            --                 -8%               -61%        -76%
short_array_borodin   10929/s                   57%            8%                  --               -57%        -74%
short_array_sodved    25707/s                  270%          155%                135%                 --        -40%
short_array           42553/s                  513%          322%                289%                66%          --
                      Rate mid_array_borodin mid_recurse_borodin mid_string mid_array_sodved mid_array
mid_array_borodin   1418/s                --                -28%       -56%             -65%      -82%
mid_recurse_borodin 1972/s               39%                  --       -39%             -52%      -76%
mid_recurse         3226/s              127%                 64%         --             -21%      -60%
mid_array_sodved    4082/s              188%                107%        27%               --      -49%
mid_array           8065/s              469%                309%       150%              98%        --
                       Rate long_array_borodin long_array_sodved long_recurse_borodin long_array long_string
long_array_borodin    172/s                 --              -67%                 -80%       -85%        -89%
long_array_sodved     513/s               199%                --                 -40%       -55%        -67%
long_recurse_borodin  854/s               397%               66%                   --       -25%        -45%
long_array           1142/s               564%              122%                  34%         --        -26%
long_recurse         1546/s               800%              201%                  81%        35%          --
4

3 に答える 3

1

予約にもかかわらず、再帰的なアプローチを示すようにソリューションを更新しました。ベンチマークはあなた次第です!結果を投稿してください。

再帰、または分割統治アプローチは、この問題には適切ではありません。最後に、文字のすべてのペアを比較し、連続して一致する文字の数を評価する必要があります。これを一度に行う場合でも、文字列を2つに分割し、それぞれの半分を個別に処理して結果を再結合する場合でも、違いはありません。実際、中間結果を分割して結合するために必要なコードのために、再帰的ソリューションは遅くなるはずです。

この問題は、両方の文字列を個々の文字に分割し、2つのシーケンスの文字の各ペアを比較することで解決する必要があります。

このソリューションは、必要なことを実行しているようであり、2つのストリングの長さが異なる場合も考慮に入れています。

use strict;
use warnings;

use Data::Dump;

my $str1 = "aaaaaaaaaaaabbbbbbbbbbbccccccccdddddddddddeeeefffffff";
my $str2 = "aaaaaaaaaaaabbbbbbbbbbbccccccccxxxxxxxddxxeeeefffffff";

dd [ find_diff( 'xaabbb', 'aaabbc' ) ];
dd [ find_diff( 'aaabbb', 'aaabbc' ) ];
dd [ find_diff( $str1, $str2 ) ];
dd [ find_diff( 'xxx', 'xx' ) ];

sub find_diff {

  my @str1 = unpack '(A1)*', shift;
  my @str2 = unpack '(A1)*', shift;
  my @return;
  my $nmatch;

  while (@str1 or @str2) {
    my @pair = map $_ // '', ( shift(@str1), shift(@str2) );
    if ($pair[0] eq $pair[1]) {
      $nmatch++;
    }
    else {
      push @return, $nmatch if $nmatch;
      undef $nmatch;
      push @return, $pair[1];
    }
  }
  push @return, $nmatch if $nmatch;

  return @return;
}

出力

["a", 4, "c"]
[5, "c"]
[31, "x", "x", "x", "x", "x", "x", "x", 2, "x", "x", 11]
[2, ""]

アップデート

同等の再帰的ソリューションに対する要求を満たすために、このサブルーチンは再帰的アプローチを使用して同じことを行います。長さが異なる比較対象の文字列のペアが提供された場合に死ぬことを除いて、同じものを生成します。

元の文字列のデータが完全に非数値であることに依存していることに注意してください。そうでない場合、問題はより複雑になります。

アップデート2

recursive_find_diff数字を含む文字列を正しく処理するように変更しました。一致する文字の数でない限り、結果のリストのメンバーはすべて単一文字であることに依存します。そこで、+すべての一致カウントの前に追加して、一貫して1文字より長くし、区別しやすくしました。

この複雑さはすべて、単純な解決策よりもはるかに遅くなると確信しています。

use strict;
use warnings;

use Data::Dump;

my $str1 = "aaaaaaaaaaaabbbbbbbbbbbccccccccdddddddddddeeeefffffff";
my $str2 = "aaaaaaaaaaaabbbbbbbbbbbccccccccxxxxxxxddxxeeeefffffff";

dd [ recursive_find_diff( 'xaabbb', 'aaabbc' ) ];
dd [ recursive_find_diff( 'aaabbb', 'aaabbc' ) ];
dd [ recursive_find_diff( $str1, $str2 ) ];
dd [ recursive_find_diff( '111222444888', '11122233488x' ) ];

sub recursive_find_diff {

  my ($str1, $str2) = @_;
  my $len = length $str1;

  die "Strings for comparison must be of equal lengths" unless length $str2 == $len;

  if ($str1 eq $str2) {
    return ( '+'.$len );
  }
  elsif ($len == 1) {
    return $str1 eq $str2 ? ( '+1' ) : ( $str2 );
  }
  else {
    my $half = int($len / 2);
    my @part1 = recursive_find_diff(substr($str1, 0, $half), substr($str2, 0, $half));
    my @part2 = recursive_find_diff(substr($str1, $half), substr($str2, $half));
    if (length $part1[-1] >1 and length $part2[0] > 1) {
      $part2[0] = '+'.($part2[0] + pop @part1);
    }
    return ( @part1, @part2 );
  }
}

出力

["a", "+4", "c"]
["+5", "c"]
["+31", "x", "x", "x", "x", "x", "x", "x", "+2", "x", "x", "+11"]
["+6", 3, 3, "+3", "x"]
于 2012-07-18T16:38:57.107 に答える
1

Borodin と Sodved のおかげで、ソリューションがかなり高速になるまで改善されました。比較している文字列は、値の変更を除いてほぼ同じログ メッセージであるため、再帰的なソリューションを使用すると、膨大な量の作業が不要になります。

Sodved が述べたように、文字ごとの比較を行う必要があるため、C では同様の利点はありません。

現在行われているのは、文字列の長さが特定のしきい値を下回っていることを確認し、そうである場合は配列比較にフォールバックすることです。

パフォーマンスは次のようになります。

                        Rate          long_recurse long_recurse_fallback
long_recurse          1613/s                    --                  -18%
long_recurse_fallback 1961/s                   22%                    --

これが私の最終的なコードです(テスト文字列が削除され、実際のログメッセージになります):

use strict;
use warnings;
use Data::Dumper;
use Benchmark qw(cmpthese);
$Data::Dumper::Indent = 0;
$Data::Dumper::Terse  = 1;

my $str1 = "aaaaaaaaaaaabbbbbbbbbbbccccccccdddddddddddeeeefffffff";
my $str2 = "aaaaaaaaaaaabbbbbbbbbbbccccccccxxxxxxxddxxeeeefffffff";

sub find_diff {
    my ( $a, $b, $minlen ) = @_;
    my $len = length $a;
    if ($len < $minlen) {
        return compress_unpack_ary( $a, $b );
    }
    if ( $len < 2 ) {
        return [ord($b), 0];
    }
    my @rtn = ();
    my $div = $len / 2;
    my $a_1 = substr $a, 0, $div;
    my $b_1 = substr $b, 0, $div;
    if ($a_1 eq $b_1) {
        push @rtn, [length $a_1, 1];
    }
    else {
        push @rtn, find_diff( $a_1, $b_1, $minlen );
    }
    my $a_2 = substr $a, $div;
    my $b_2 = substr $b, $div;
    if ($a_2 eq $b_2) {
        push @rtn, [length $a_2, 1];
    }
    else {
        push @rtn, find_diff( $a_2, $b_2, $minlen );
    }
    return @rtn;
}

sub compress_string {
    my ($a, $b, $minlen) = @_;
    my @list = find_diff($a, $b, $minlen);
    my $acc = 0;
    my @result = ();
    foreach my $item (@list) {
        if ( $item->[1] ) {
            $acc += $item->[0];
        } else {
            while ( $acc > 127 ) {
                push @result, 255;
                $acc -= 127;
            }
            push @result, $acc + 128 if $acc;
            push @result, $item->[0];
            $acc = 0;
        }
    }
    while ( $acc > 127 ) {
        push @result, 255;
        $acc -= 127;
    }
    push @result, $acc + 128 if $acc;
    return pack('C*', @result);
}
sub compress_unpack_ary {
    my ( $a, $b ) = @_;
    my @orig       = unpack('C*', $a);
    my @new        = unpack('C*', $b);
    my @nonmatches = ();
    my $count      = 0;
    my $repeats    = 0;
    while ( $count < scalar @new ) {
        if ( $orig[$count] and $new[$count] == $orig[$count] ) {
            $repeats++;
        }
        elsif ( $repeats == 1 ) {
            push @nonmatches, [ $new[$count - 1], 0], [$new[$count], 0];
            $repeats = 0;
        }
        elsif ( $repeats > 1 ) {
            push @nonmatches, [$repeats, 1];
            $repeats = 0;    # reset counter
            push @nonmatches, [$new[$count], 0];
        }
        else {
            push @nonmatches, [$new[$count], 0];
        }
        $count++;
    }
    if ( $repeats > 0 ) {
        push @nonmatches, [$repeats, 1];
    }
    return @nonmatches;
}
print Data::Dumper::Dumper( [ compress_string( $str1, $str2, 20 ) ] ) . "\n";
print Data::Dumper::Dumper( [ compress_string( $str1, $str2, 0 ) ] ) . "\n";
print Data::Dumper::Dumper( [ compress_string( $long_a, $long_b, 20 ) ] ) . "\n";
print Data::Dumper::Dumper( [ compress_string( $long_a, $long_b, 0 ) ] ) . "\n";

cmpthese(1000, {
        'long_recurse' => sub { compress_string($long_a, $long_b, 0 ) },
        'long_recurse_fallback' => sub { compress_string($long_a, $long_b, 20 ) },
        });
于 2012-07-19T15:53:22.497 に答える
1

編集:おっと、ごめんなさい。再帰を使用して文字列を分割したいというコメントを見ました。ですから、私の答えはあまり適切ではありません。申し訳ありません。とにかく残します。

再帰は必要ないと思います。以下の作品

use Data::Dumper;

sub find_diff($$)
{
    my( $a, $b ) = @_;
    my @res;
    my @a = split( '', $a );
    my @b = split( '', $b );
    # Assume a and b are the same length
    my $mcount = 0;
    for( my $i = 0; $i < scalar(@a); $i++ )
    {
        if( $a[$i] eq $b[$i] )
        {
            $mcount++;
        }
        else
        {
            if( $mcount )
            {
                push( @res, $mcount );
            }
            $mcount = 0;
            push( @res, $b[$i] );
        }
    }
    if( $mcount )
    {
        push( @res, $mcount );
    }
    return @res;
} # END find_diff

print Data::Dumper::Dumper( [ find_diff('xaabbb', 'aaabbc' ) ] ) . "\n";
print Data::Dumper::Dumper( [ find_diff('aaabbb', 'aaabbc' ) ] ) . "\n";
于 2012-07-18T16:20:00.247 に答える