如何将矩阵的单层旋转n个位置?

时间:2019-01-25 03:51:31

标签: arrays perl matrix multidimensional-array 2d

我已经阅读了有关旋转矩阵的所有堆栈溢出问题 除了在单层或整个矩阵的任一方向上转置或旋转90度或180度外,它们都没有解决任何其他问题。

我已经回答了有关完成该问题的问题,但是我正在寻找快速和/或“就地”方法,最好是任何方法 除了我给的以外,如果只是为了 教育目的。我的示例在Perl 5中,但是只要您的意图很明确,大多数任何语言都应该可以接受。

我的示例中使用的矩阵如下

# 2D array @arr has 3 layers.
# @arr is 
#         0 1 2 3 4 5
#         a b c d e f
#         5 4 3 2 1 0
#         9 8 7 6 5 4
#         f e d c b a
#         4 5 6 7 8 9

Goal->顺时针旋转中间2层以获得以下内容...

请注意中间层'b c'的左上角如何向右移动2个位置,而从中间左侧开始的'8 4'现在是'b c'的位置。

# Rotated @arr is 
#         0 1 2 3 4 5
#         a 8 4 b c f
#         5 e 3 2 d 0
#         9 d 7 6 e 4 
#         f c b 5 1 a
#         4 5 6 7 8 9

目前,我一直在做一些类似的事情,这确实很慢。尤其是在许多大型阵列上。

#!/usr/bin/env perl
use strict; use warnings;

# Coordinates are $arr[$row][$col]
my @arr;
$arr[0][0]='0'; $arr[0][1]='1'; $arr[0][2]='2'; $arr[0][3]='3'; $arr[0][4]='4'; $arr[0][5]='5';
$arr[1][0]='a'; $arr[1][1]='b'; $arr[1][2]='c'; $arr[1][3]='d'; $arr[1][4]='e'; $arr[1][5]='f';
$arr[2][0]='5'; $arr[2][1]='4'; $arr[2][2]='3'; $arr[2][3]='2'; $arr[2][4]='1'; $arr[2][5]='0';
$arr[3][0]='9'; $arr[3][1]='8'; $arr[3][2]='7'; $arr[3][3]='6'; $arr[3][4]='5'; $arr[3][5]='4';
$arr[4][0]='f'; $arr[4][1]='e'; $arr[4][2]='d'; $arr[4][3]='c'; $arr[4][4]='b'; $arr[4][5]='a';
$arr[5][0]='4'; $arr[5][1]='5'; $arr[5][2]='6'; $arr[5][3]='7'; $arr[5][4]='8'; $arr[5][5]='9';

# Print matrix
print_matrix(@arr);

# Extract layer 2
my $target_layer=2;
my $layer_two=extract_layer($target_layer,@arr);

# From the top left corner of the layer, it is as follows
# bcde15bcde84
print "\n$layer_two\n";

# Rotate layer 2 clockwise 2 places
$layer_two=rotate_layer_cl($layer_two,2);

# 84bcde15bcde
print "$layer_two\n\n";

# Replace layer 2 in the same way
@arr=replace_layer($target_layer,$layer_two,@arr);

# Print again
print_matrix(@arr);

### Sub functions ###
# Extract layer by walking around it's coordinates like so
# [1,1]-[1,4]  Top(left->right)
# [2,4]-[4,4]  Right(top->bottom)
# [4,3]-[4,1]  Bottom(right->left)
# [3,1]-[2,1]  Left(bottom->top)
sub extract_layer {
    my ($layer_cnt,@matrix)=@_;
    my $width=scalar(@matrix);
    my $layer_width=$width-$layer_cnt;
    # layer_cnt=2
    # width=6
    # layer_width=4
    my $layer;
    for my $col ( $layer_cnt-1..$layer_width ) {
        $layer.=$matrix[$layer_cnt-1][$col];
    }
    for my $row ( $layer_cnt..$layer_width ) {
        $layer.=$matrix[$row][$layer_width];
    }
    my $cnt=$layer_width-1;
    while ( $cnt >= $layer_cnt-1 ) {
        $layer.=$matrix[$layer_width][$cnt];
        $cnt--;
    }
    $cnt=$layer_width-1;
    while ( $cnt >= $layer_cnt ) {
        $layer.=$matrix[$cnt][$layer_cnt-1];
        $cnt--;
    }
    return $layer;
}

# Shift input to the right by $n places, wrapping around.
sub rotate_layer_cl {
    my $n=$_[1];
    my $buf=substr($_[0],length($_[0])-$n,$n);
    return $buf.substr($_[0],0,length($_[0])-$n);
}

# Replace each char from the rotated layer.
sub replace_layer {
    my ($layer_cnt,$layer,@matrix)=@_;
    my $width=scalar(@matrix);
    my $layer_width=$width-$layer_cnt;
    # layer_cnt=2
    # width=6
    # layer_width=4
    my $slot=0;
    for my $col ( $layer_cnt-1..$layer_width ) {
        $matrix[$layer_cnt-1][$col]=substr($layer,$slot,1);
        $slot++;
    }
    for my $row ( $layer_cnt..$layer_width ) {
        $matrix[$row][$layer_width]=substr($layer,$slot,1);
        $slot++;
    }
    my $cnt=$layer_width-1;
    while ( $cnt >= $layer_cnt-1 ) {
        $matrix[$layer_width][$cnt]=substr($layer,$slot,1);
        $slot++;
        $cnt--;
    }
    $cnt=$layer_width-1;
    while ( $cnt >= $layer_cnt ) {
        $matrix[$cnt][$layer_cnt-1]=substr($layer,$slot,1);
        $slot++;
        $cnt--;
    }
    return @matrix;
}

# Prints given matrix
sub print_matrix {
    foreach my $row (@_) {
        my $cnt=0;
        foreach my $char (@$row) {
            print $char; $cnt++;
            if ( $cnt == scalar(@_) ) {
                print "\n";
            } else {
                print " ";
            }
        }
    }
}

上面的代码输出如下,

0 1 2 3 4 5
a b c d e f
5 4 3 2 1 0
9 8 7 6 5 4
f e d c b a
4 5 6 7 8 9

bcde15bcde84
84bcde15bcde

0 1 2 3 4 5
a 8 4 b c f
5 e 3 2 d 0
9 d 7 6 e 4
f c b 5 1 a
4 5 6 7 8 9

其中显示了旋转中间层之前的数组,中间层作为字符串,中间层作为字符串移位,以及中间层向右旋转2个位置后的最终结果数组。

-----编辑-----

到目前为止,我发现最快的方法是不实际使用完整的2D数组,而是将块放置在常规数组中,例如...

my @hex_ary=('1234', '5678', '90ab', 'cdef');

然后创建图层字符串,并按照我的原始示例所示的方式进行移动。

但是,因为它们是琴弦,所以我只需要上下左右行走即可。顶部和底部简称为...

my $top=substr($hex_ary[0],0,3);

my $bot=reverse(substr($hex_ary[3],0,3));

在这个小型4x4阵列中的外层,而侧面则通过进行遍历

my $right_side_char=substr($hex_ary[$top_row-$i],-1);
my $left_side_char=substr($hex_ary[$bot_row+$i],0,1);

这样做可以提高性能。之所以采用2D方法,是因为100%是因为从数组中获取的切片数是一半+2。

也许对C有更好了解的人可以创建更有效的东西。我觉得Perl有时候只能做到这么多。

2 个答案:

答案 0 :(得分:1)

简而言之:

  • 复制数组
  • 计算该环的成员列表(行,列)
  • 遍历列表索引

    • 从原始数组中的成员[index]中读取
    • 写入副本数组中的成员[(index + shift)%环长度]
  • 右移N个位置:shift == N

  • 左移N个位置:shift == -N
#!/usr/bin/perl
use strict;
use warnings;

sub dump_array($$) {
    my($label, $array) = @_;
    print "${label}:\n";
    foreach my $row (@{ $array }) {
        print join(" ", @{ $row }), "\n";
    }
    print "\n";
}

sub copy_array($) {
    my($array) = @_;
    my @copy;
    foreach my $row (@{ $array }) {
        push(@copy, [ @{ $row } ]);
    }
    return(\@copy);
}

sub ring_index_to_row_col($$$$$) {
    my($N, $ring, $total, $length, $index) = @_;
    my($row, $col);

    if ($index < $length) {
        # top row, left to right
        $row = 0;
        $col = $index;
    } elsif ($index < 2 * $length - 2) {
        # top to bottom, right row
        $row = $index - $length + 1;
        $col = $N - 1 - 2 * $ring;
    } elsif ($index < 3 * $length - 2) {
        # bottom row, right to left
        $row = $N - 1 - 2 * $ring;
        #$col = $length - 1 - ($index - 2 * $length + 2);
        $col = -$index + 3 * $length - 3;
    } else {
        # bottom to top, left row
        #$row = $total - 1 - $index + 1;
        $row = $total - $index;
        $col = 0;
    }

    #print "${index}\t of ${total}\t-> ${row}, ${col}\n";

    # shift $length x length array to offset ($ring, $ring)
    return([$row + $ring, $col + $ring]);
}

sub ring_members($$) {
    my($N, $ring) = @_;
    my @list;

    # @TODO: odd N?
    #
    # Examples for N == 6
    #   0   -> 2*6 + 2*4 = 20
    #   1   -> 2*4 + 2*2 = 12
    #   2   -> 2*2 + 2*0 =  4
    #
    # Examples for N == 5
    #   0   -> 2*5 + 2*3
    #   1   -> 2*3 + 2*1
    #   2   -> 1
    #
    # Examples for N == 4
    #   0   -> 2*4 + 2*2 = 12
    #   1   -> 2*2 + 2*0 =  4
    my $length  = $N - 2 * $ring;
    my $members = 4 *  $N - 8 * $ring - 4;

    foreach my $index (0..$members-1) {
        push(@list, ring_index_to_row_col($N, $ring, $members, $length, $index));
    }

    return(\@list);
}

sub rotate_array_ring(\@$$) {
    my($source, $ring, $shift) = @_;

    # Sanity check. @TODO is the check correct for odd N?
    my $N              = @{ $source };
    die "ERROR: invalid ring '${ring}' for 2D array of size $N!\n"
        unless $ring < ($N / 2);

    my $copy   = copy_array($source);

    my $list   = ring_members($N, $ring);
    my $length = @{ $list };

    foreach my $index (0..$length-1) {
        my($row,   $col)   = @{ $list->[ $index                     ] };
        my($s_row, $s_col) = @{ $list->[($index + $shift) % $length ] };

        $copy->[$s_row]->[$s_col] = $source->[$row]->[$col];
    }

    return($copy);
}

my @source;
while (<DATA>) {
    chomp;
    push(@source, [ split(/\s+/, $_) ]);
}
dump_array('SOURCE', \@source);

dump_array('SHIFT 1 RING 0', rotate_array_ring(@source, 0, 1));
dump_array('SHIFT 1 RING 1', rotate_array_ring(@source, 1, 1));
dump_array('SHIFT 1 RING 2', rotate_array_ring(@source, 2, 1));
dump_array('SHIFT 2 RING 0', rotate_array_ring(@source, 0, 2));
dump_array('SHIFT 2 RING 1', rotate_array_ring(@source, 1, 2));
dump_array('SHIFT 2 RING 2', rotate_array_ring(@source, 2, 2));

exit 0;

__DATA__
0 1 2 3 4 5
a b c d e f
5 4 3 2 1 0
9 8 7 6 5 4
f e d c b a
4 5 6 7 8 9

示例输出:

$ perl dummy.pl
SOURCE:
0 1 2 3 4 5
a b c d e f
5 4 3 2 1 0
9 8 7 6 5 4
f e d c b a
4 5 6 7 8 9

SHIFT 1 RING 0:
a 0 1 2 3 4
5 b c d e 5
9 4 3 2 1 f
f 8 7 6 5 0
4 e d c b 4
5 6 7 8 9 a

SHIFT 1 RING 1:
0 1 2 3 4 5
a 4 b c d f
5 8 3 2 e 0
9 e 7 6 1 4
f d c b 5 a
4 5 6 7 8 9

SHIFT 1 RING 2:
0 1 2 3 4 5
a b c d e f
5 4 7 3 1 0
9 8 6 2 5 4
f e d c b a
4 5 6 7 8 9

SHIFT 2 RING 0:
5 a 0 1 2 3
9 b c d e 4
f 4 3 2 1 5
4 8 7 6 5 f
5 e d c b 0
6 7 8 9 a 4

SHIFT 2 RING 1:
0 1 2 3 4 5
a 8 4 b c f
5 e 3 2 d 0
9 d 7 6 e 4
f c b 5 1 a
4 5 6 7 8 9

SHIFT 2 RING 2:
0 1 2 3 4 5
a b c d e f
5 4 6 7 1 0
9 8 2 3 5 4
f e d c b a
4 5 6 7 8 9

优化:不复制整个数组,但是

  1. 使用$list将环成员复制到列表中,然后
  2. 在该列表上循环以将它们放回已移位的位置,并重新放置到数组中。

即两个循环而不是一个循环:

my $list   = ring_members($N, $ring);
my $length = @{ $list };

# in-place approach
my @members;
foreach my $index (0..$length-1) {
    my($row, $col) = @{ $list->[ $index ] };
    push(@members, $source->[$row]->[$col]);
}
foreach my $index (0..$length-1) {
    my($row, $col) = @{ $list->[ ($index + $shift) % $length ] };
    $source->[$row]->[$col] = $members[$index];
}

return($source);

优化2:如果您需要将一系列操作应用于一组相同大小的2D数组,则可以预先生成它们并一遍又一遍地处理它们使用功能:

my $N     = 6; # all arrays are of same size
my @rings = (
    ring_members($N, 0),
    ring_members($N, 1),
    ring_members($N, 2),
);
my @operations = (
    { ring => $rings[0], shift =>  1 },
    { ring => $rings[1], shift => -1 },
    { ring => $rings[2], shift =>  2 },
    # ...
)

# apply one operation in-place on array
sub apply_operation($$) {
    my($array, $operation) = @_;
    my $list               = $operation->{ring};
    my $shift              = $operation->{shift};
    my $length             = @{ $list };

    my @members;
    foreach my $index (0..$length-1) {
        my($row, $col) = @{ $list->[ $index ] };
        push(@members, $array->[$row]->[$col]);
    }
    foreach my $index (0..$length-1) {
        my($row, $col) = @{ $list->[ ($index + $shift) % $length ] };
        $array->[$row]->[$col] = $members[$index];
    }
}

# apply operation sequence in-place on array
sub apply_operations($$) {
    my($array, $operations) = @_;

    foreach my $operation (@{ $operations }) {
        apply_operation($array, $operation);    
    }
}

apply_operations(\@array1, \@operations);
apply_operations(\@array2, \@operations);
apply_operations(\@array3, \@operations);
...

优化3:不会使用就地方法复制完整的组,而只会复制那些可能被轮换覆盖的成员。即使用3个循环:

  1. 将第一个或最后一个$shift成员(取决于旋转方向)复制到@cache数组中。
  2. 直接复制其他$length - abs($shift)个成员。
  3. @cache个成员复制到其轮换的位置。

优化4:不使用2D数组(AoA),而是使用包含$N * $N个条目的1D数组。即

  • 您将有ring_index_to_row_col()返回[$row, $col],而不是ring_index_to_array_index()返回$row * $N + $col
  • 数组访问->[$row]->[$col]将替换为->[$array_index]

答案 1 :(得分:1)

我的方法的想法是使用包含n的{​​{3}}(好吧,我在这里使用Perl数组是因为我找不到合适的模块,但它远非理想)。元素(其中n是要旋转图层的位置的数量),并遍历该图层的每个元素,并且每次将push的值放入队列中并替换与队列的第一个元素。

这是子rotate_matrix的工作。
 -$i$j代表图层当前元素的索引。它们的值介于$target_layer$max_i / $max_j之间;子next_ij负责计算层的下一个元素的索引。
 -有趣的事情发生在while(1)循环内(实际上是do...while;但是do while循环在Perl中是queue):当前元素的值被压入队列,如果它们在队列中超过$n个元素(这意味着我们已经推送了$n个元素,表示我们想旋转$n个事实),我们将其值替换为队列的第一个元素。 continue块负责递增$i$j,并在返回到图层的第一个元素后停止循环。
-完成循环后,该图层的前n个元素尚未更新(因为此next unless @queue > $n),因此我们需要注意:这就是{{1 }}循环)。

复杂度方面,无需复制数组。内存使用量为while (@queue)(队列的大小)。在时间上,您遍历该图层的每个元素一次,并且每次您从队列中O(n)push(使用更好的数据结构,如链接列表,这将是{{1 }},但使用Perl数组,它可能位于shift的行中;但可能摊销到O(1)),并计算下一个索引(O(n))。因此,您最终会浪费时间,最终会遇到O(1)

一些警告:如果O(1)大于图层中的元素数量,则将无法正常工作(但可以使用简单的模数对其进行修复)。如果您要的是矩阵中不存在的一层(例如2x2矩阵的第4层),则会发生怪异的事情(但是再一次,很容易修复)。

O(size of the layer to rotate)