recursive_matrix_multiplication.pl 4.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213
  1. #!/usr/bin/perl
  2. # Author: Daniel "Trizen" Șuteu
  3. # License: GPLv3
  4. # Date: 04 April 2016
  5. # Website: https://github.com/trizen
  6. # Recursive matrix multiplication, using a divide and conquer algorithm.
  7. # See also: https://en.wikipedia.org/wiki/Matrix_multiplication
  8. # NOTE: works only with n*n matrices, where n must be a power of 2.
  9. use 5.010;
  10. use strict;
  11. use warnings;
  12. sub add {
  13. my ($A, $B) = @_;
  14. my $C = [[]];
  15. foreach my $i (0 .. $#{$A}) {
  16. foreach my $j (0 .. $#{$A->[$i]}) {
  17. $C->[$i][$j] += $A->[$i][$j] + $B->[$i][$j];
  18. }
  19. }
  20. $C;
  21. }
  22. sub msplit {
  23. my ($A, $B, $C, $D) = @_;
  24. my $end = $#{$A};
  25. my $mid = int($end / 2);
  26. my @A = @{$A}[0 .. $mid];
  27. my @B = @{$B}[0 .. $mid];
  28. my @C = @{$A}[$mid + 1 .. $end];
  29. my @D = @{$B}[$mid + 1 .. $end];
  30. my @E = @{$C}[0 .. $mid];
  31. my @F = @{$D}[0 .. $mid];
  32. my @G = @{$C}[$mid + 1 .. $end];
  33. my @H = @{$D}[$mid + 1 .. $end];
  34. #<<<
  35. if ($end > 3) {
  36. return
  37. msplit(\@A, \@C, \@B, \@D),
  38. msplit(\@E, \@G, \@F, \@H);
  39. }
  40. #>>>
  41. #<<<
  42. [
  43. [@A, @B],
  44. [@C, @D],
  45. [@E, @F],
  46. [@G, @H],
  47. ]
  48. #>>>
  49. }
  50. #
  51. ## Known issue: broken
  52. #
  53. sub merge_rows {
  54. my (@blocks) = @_;
  55. if (@{$blocks[0]} > 4) {
  56. my @merged;
  57. while (@{$blocks[0]}) {
  58. my @rows;
  59. foreach my $block (@blocks) {
  60. push @rows, [splice(@{$block}, 0, 4)];
  61. }
  62. push @merged, @{merge_rows(@rows)};
  63. }
  64. return \@merged;
  65. }
  66. my @A;
  67. foreach my $i (0 .. 3) {
  68. push @{$A[$i]}, @{$blocks[0][$i]}, @{$blocks[1][$i]};
  69. push @{$A[$i + 4]}, @{$blocks[2][$i]}, @{$blocks[3][$i]};
  70. }
  71. return \@A;
  72. }
  73. #
  74. ## Known issue: broken
  75. #
  76. sub merge {
  77. my (@blocks) = @_;
  78. while (@blocks > 4) {
  79. push @blocks, merge_rows(splice(@blocks, 0, 4));
  80. }
  81. return merge_rows(@blocks);
  82. }
  83. sub mul {
  84. my ($A, $B) = @_;
  85. ## Base case:
  86. #<<<
  87. if ($#{$A} == 1 and $#{$A->[0]} == 1 and $#{$B} == 1 and $#{$B->[0]} == 1) {
  88. return [
  89. [
  90. $A->[0][0] * $B->[0][0] + $A->[0][1] * $B->[1][0],
  91. $A->[0][0] * $B->[0][1] + $A->[0][1] * $B->[1][1],
  92. ],
  93. [
  94. $A->[1][0] * $B->[0][0] + $A->[1][1] * $B->[1][0],
  95. $A->[1][0] * $B->[0][1] + $A->[1][1] * $B->[1][1],
  96. ],
  97. ];
  98. }
  99. #>>>
  100. my $end = $#{$A};
  101. my $mid = int($end / 2);
  102. my @A = map { [@{$_}[0 .. $mid]] } @{$A}[0 .. $mid];
  103. my @B = map { [@{$_}[$mid + 1 .. $end]] } @{$A}[0 .. $mid];
  104. my @C = map { [@{$_}[0 .. $mid]] } @{$A}[$mid + 1 .. $end];
  105. my @D = map { [@{$_}[$mid + 1 .. $end]] } @{$A}[$mid + 1 .. $end];
  106. my @E = map { [@{$_}[0 .. $mid]] } @{$B}[0 .. $mid];
  107. my @F = map { [@{$_}[$mid + 1 .. $end]] } @{$B}[0 .. $mid];
  108. my @G = map { [@{$_}[0 .. $mid]] } @{$B}[$mid + 1 .. $end];
  109. my @H = map { [@{$_}[$mid + 1 .. $end]] } @{$B}[$mid + 1 .. $end];
  110. #<<<
  111. [
  112. (
  113. [map{@{$_}} @{add(mul(\@A, \@E), mul(\@B, \@G))}],
  114. [map{@{$_}} @{add(mul(\@A, \@F), mul(\@B, \@H))}],
  115. [map{@{$_}} @{add(mul(\@C, \@E), mul(\@D, \@G))}],
  116. [map{@{$_}} @{add(mul(\@C, \@F), mul(\@D, \@H))}]
  117. ),
  118. ];
  119. #>>>
  120. }
  121. sub mmult {
  122. our @a;
  123. local *a = shift;
  124. our @b;
  125. local *b = shift;
  126. my @p = [];
  127. my $rows = @a;
  128. my $cols = @{$b[0]};
  129. my $n = @b - 1;
  130. for (my $r = 0 ; $r < $rows ; ++$r) {
  131. for (my $c = 0 ; $c < $cols ; ++$c) {
  132. foreach (0 .. $n) {
  133. $p[$r][$c] += $a[$r][$_] * $b[$_][$c];
  134. }
  135. }
  136. }
  137. return [@p];
  138. }
  139. sub new_matrix {
  140. my ($n) = @_;
  141. [map { [$n * $_ - $n + 1 .. $_ * $n] } 1 .. $n];
  142. }
  143. sub display_matrix {
  144. my ($A, $w) = @_;
  145. say join(
  146. "\n",
  147. map {
  148. join(' ', map { sprintf("%${w}d", $_) } @{$_})
  149. } @{$A}
  150. );
  151. }
  152. #
  153. ## Demo:
  154. #
  155. my $A = [[3, 4], [5, 6]];
  156. use Data::Dump qw(pp);
  157. pp mul($A, $A);
  158. pp mmult($A, $A);
  159. my $B = new_matrix(4);
  160. pp mmult($B, $B);
  161. pp mul($B, $B);
  162. my $C = new_matrix(8);
  163. my $D = mmult($C, $C);
  164. display_matrix($D, 6);
  165. my $x = mul($C, $C);
  166. pp msplit(@{$x});