RosettaCodeData/Task/Strassens-algorithm/Perl/strassens-algorithm.pl
2026-04-30 12:34:36 -04:00

256 lines
6.2 KiB
Perl

#!/usr/bin/perl
use strict;
use warnings;
use List::Util qw(sum);
# Matrix class implemented as array reference
package Matrix;
# Constructor
sub new {
my ($class, $data) = @_;
my $self = defined $data ? $data : [];
bless $self, $class;
return $self;
}
# Create matrix from nested blocks
sub block {
my ($class, $blocks) = @_;
my @result;
# Get dimensions
my $num_rows = 0;
for my $hblock (@$blocks) {
$num_rows += @{$hblock->[0]};
}
# Process each horizontal block
my $row_offset = 0;
for my $hblock (@$blocks) {
my $block_height = @{$hblock->[0]};
# Zip and concatenate rows
for my $i (0..$block_height-1) {
my @new_row;
for my $matrix (@$hblock) {
push @new_row, @{$matrix->[$i]};
}
$result[$row_offset + $i] = \@new_row;
}
$row_offset += $block_height;
}
return Matrix->new(\@result);
}
# Matrix multiplication (naive)
sub dot {
my ($self, $b) = @_;
my ($rows_a, $cols_a) = $self->shape();
my ($rows_b, $cols_b) = $b->shape();
die "Matrix dimensions don't match for multiplication" unless $cols_a == $rows_b;
my @result;
for my $i (0..$rows_a-1) {
my @row;
for my $j (0..$cols_b-1) {
my $sum = 0;
for my $k (0..$cols_a-1) {
$sum += $self->[$i][$k] * $b->[$k][$j];
}
push @row, $sum;
}
push @result, \@row;
}
return Matrix->new(\@result);
}
# Overload multiplication operator
use overload '*' => \˙
# Matrix addition
sub add {
my ($self, $b) = @_;
my ($rows, $cols) = $self->shape();
my ($b_rows, $b_cols) = $b->shape();
die "Matrix dimensions don't match for addition" unless $rows == $b_rows && $cols == $b_cols;
my @result;
for my $i (0..$rows-1) {
my @row;
for my $j (0..$cols-1) {
push @row, $self->[$i][$j] + $b->[$i][$j];
}
push @result, \@row;
}
return Matrix->new(\@result);
}
# Matrix subtraction
sub subtract {
my ($self, $b) = @_;
my ($rows, $cols) = $self->shape();
my ($b_rows, $b_cols) = $b->shape();
die "Matrix dimensions don't match for subtraction" unless $rows == $b_rows && $cols == $b_cols;
my @result;
for my $i (0..$rows-1) {
my @row;
for my $j (0..$cols-1) {
push @row, $self->[$i][$j] - $b->[$i][$j];
}
push @result, \@row;
}
return Matrix->new(\@result);
}
# Overload operators
use overload '+' => \&add;
use overload '-' => \&subtract;
# Strassen's algorithm
sub strassen {
my ($self, $b) = @_;
my ($rows, $cols) = $self->shape();
my ($b_rows, $b_cols) = $b->shape();
die "Matrices must be square" unless $rows == $cols && $b_rows == $b_cols;
die "Matrices must be the same shape" unless $rows == $b_rows;
die "Shape must be a power of 2" unless $rows > 0 && ($rows & ($rows - 1)) == 0;
if ($rows == 1) {
return $self->dot($b);
}
my $p = $rows / 2;
# Partition matrices
my $a11 = Matrix->new([map { [ @{$self->[$_]}[0..$p-1] ] } 0..$p-1]);
my $a12 = Matrix->new([map { [ @{$self->[$_]}[$p..$rows-1] ] } 0..$p-1]);
my $a21 = Matrix->new([map { [ @{$self->[$_]}[0..$p-1] ] } $p..$rows-1]);
my $a22 = Matrix->new([map { [ @{$self->[$_]}[$p..$rows-1] ] } $p..$rows-1]);
my $b11 = Matrix->new([map { [ @{$b->[$_]}[0..$p-1] ] } 0..$p-1]);
my $b12 = Matrix->new([map { [ @{$b->[$_]}[$p..$b_cols-1] ] } 0..$p-1]);
my $b21 = Matrix->new([map { [ @{$b->[$_]}[0..$p-1] ] } $p..$b_rows-1]);
my $b22 = Matrix->new([map { [ @{$b->[$_]}[$p..$b_cols-1] ] } $p..$b_rows-1]);
# Calculate M1..M7
my $m1 = ($a11 + $a22)->strassen($b11 + $b22);
my $m2 = ($a21 + $a22)->strassen($b11);
my $m3 = $a11->strassen($b12 - $b22);
my $m4 = $a22->strassen($b21 - $b11);
my $m5 = ($a11 + $a12)->strassen($b22);
my $m6 = ($a21 - $a11)->strassen($b11 + $b12);
my $m7 = ($a12 - $a22)->strassen($b21 + $b22);
# Calculate C11..C22
my $c11 = $m1 + $m4 - $m5 + $m7;
my $c12 = $m3 + $m5;
my $c21 = $m2 + $m4;
my $c22 = $m1 - $m2 + $m3 + $m6;
return Matrix->block([[$c11, $c12], [$c21, $c22]]);
}
# Round elements
sub round_matrix {
my ($self, $ndigits) = @_;
$ndigits = undef unless defined $ndigits;
my @result;
for my $i (0..$#{$self}) {
my @row;
for my $j (0..$#{$self->[$i]}) {
my $val = $self->[$i][$j];
if (defined $ndigits) {
push @row, sprintf("%.${ndigits}f", $val);
} else {
push @row, int($val + ($val >= 0 ? 0.5 : -0.5));
}
}
push @result, \@row;
}
return Matrix->new(\@result);
}
# Get matrix shape
sub shape {
my ($self) = @_;
return (0, 0) unless @$self;
return (scalar @$self, scalar @{$self->[0]});
}
# String representation
sub stringify {
my ($self) = @_;
my @rows;
for my $row (@$self) {
push @rows, '[' . join(', ', @$row) . ']';
}
return '[' . join(', ', @rows) . ']';
}
use overload '""' => \&stringify;
# Examples
package main;
sub examples {
my $a = Matrix->new([
[1, 2],
[3, 4]
]);
my $b = Matrix->new([
[5, 6],
[7, 8]
]);
my $c = Matrix->new([
[1, 1, 1, 1],
[2, 4, 8, 16],
[3, 9, 27, 81],
[4, 16, 64, 256]
]);
my $d = Matrix->new([
[4, -3, 4/3, -1/4],
[-13/3, 19/4, -7/3, 11/24],
[3/2, -2, 7/6, -1/4],
[-1/6, 1/4, -1/6, 1/24]
]);
my $e = Matrix->new([
[1, 2, 3, 4],
[5, 6, 7, 8],
[9, 10, 11, 12],
[13, 14, 15, 16]
]);
my $f = Matrix->new([
[1, 0, 0, 0],
[0, 1, 0, 0],
[0, 0, 1, 0],
[0, 0, 0, 1]
]);
print "Naive matrix multiplication:\n";
print " a * b = " . ($a * $b) . "\n";
print " c * d = " . $c->round_matrix(0) . "\n";
print " e * f = " . ($e * $f) . "\n";
print "Strassen's matrix multiplication:\n";
print " a * b = " . $a->strassen($b) . "\n";
print " c * d = " . $c->strassen($d)->round_matrix(0) . "\n";
print " e * f = " . $e->strassen($f) . "\n";
}
examples() if __FILE__ eq $0;