Perhaps a bit more messy than necessary, but:
use strict;
use warnings;
my ($c, %k, %vals, %cols, @cols, @table, $row);
chomp($_ = <DATA>); s/\s+//g;
$c = 0; $k{$_} = $c++ for split /\|/, $_;
while (<DATA>) {
s/\s+//g;
@_ = split /\|/, $_;
$vals{@_[$k{'v_id'}]}{@_[$k{'p_id'}]} = @_[$k{'s_id'}];
$cols{@_[$k{'p_id'}]} = ();
}
@cols = sort { $a <=> $b } keys %cols;
push @table, ['', map { 'p_'.$_ } @cols];
for $row (sort { $a <=> $b } keys %vals) {
push @table, ['v_'.$row, map { $vals{$row}{$_} ? 's_'.$vals{$row}{
+$_} : '' } @cols];
}
table(\@table);
sub table {
my ($arr, $i, @lengths, $length, $format) = $_[0];
for (@$arr) {
for $i (0..$#$_) {
$lengths[$i] = length($_->[$i]) if !$lengths[$i] || $lengt
+hs[$i] < length($_->[$i]);
}
}
$length = 0;
$length += $_ for @lengths;
$length += 3 * $#lengths + 2;
$format = join ' | ', map { '%-'.$_.'s' } @lengths;
for ($i = 0; $i < $#$arr; $i++) {
print ' ', sprintf($format, @{$arr->[$i]}), "\n",
'-' x $length, "\n";
}
print ' ', sprintf($format, @{$arr->[-1]}), "\n";
}
__DATA__
p_id | v_id | s_id
1 | 1 | 1
2 | 1 | 2
3 | 1 | 3
4 | 1 | 4
1 | 2 | 5
2 | 2 | 6
3 | 2 | 7
4 | 2 | 8
2 | 3 | 9
4 | 3 | 10
1 | 4 | 11
2 | 4 | 12
4 | 4 | 13
3 | 5 | 14