Acme-Grep2D
view release on metacpan or search on metacpan
lib/Acme/Grep2D.pm view on Meta::CPAN
sub new {
my ($class, %opts) = @_;
my $self = \%opts;
bless $self, $class;
$.Class = $class;
./_required('text');
./_init();
return $self;
}
# check for mandatory options
sub _required {
my ($self, $name) = @_;
die "$.Class - $name is required\n" unless defined $self->{$name};
}
# adjust dimensions to be rectangular, and figure out what's
# in there in all directions
sub _init {
my ($self) = @_;
my $text = $.text;
my @text;
# split on newlines, preserving them spatially
while ((my $n = index($text, "\n")) >= 0) {
my $chunk = substr($text, 0, $n);
push(@text, $chunk);
$text = substr($text, $n+1);
}
chomp foreach @text;
my @len;
push(@len, length($_)) foreach @text;
my $maxlen = $len[0];
my $nlines = @text;
#determine max length of each string
map {
$maxlen = $len[$_] if $len[$_] > $maxlen;
} 0..($nlines-1);
# make all lines same length
map {
$text[$_] .= ' ' x ($maxlen-$len[$_]);
} 0..($nlines-1);
#print Dumper(\@text);
my @diagLR;
my @diagRL;
my @vertical;
my $x = 0;
my $y = 0;
my $max = $nlines;
$max = $maxlen if $maxlen < $nlines;
# find text along diagonal L->R
for (my $char=0; $char < $maxlen; $char++) {
my @d;
$x = $char;
my $y = 0;
my @origin = ($x, $y);
map {
if ($y < $nlines && $x < $maxlen) {
my $char = substr($text[$y], $x, 1);
push(@d, $char) if defined $char;
}
$x++;
$y++;
} 0..$nlines-1;
unshift(@d, \@origin);
push(@diagLR, \@d) if @d;
}
for (my $line=1; $line < $nlines; $line++) {
my @d;
$x = 0;
my $y = $line;
my @origin = ($x, $y);
map {
if ($y < $nlines && $x < $maxlen) {
my $char = substr($text[$y], $x, 1);
push(@d, $char) if defined $char;
}
$x++;
$y++;
} 0..$nlines-1;
unshift(@d, \@origin);
push(@diagLR, \@d) if @d;
}
# find text along diagonal R->L
for (my $char=0; $char < $maxlen; $char++) {
my @d;
$x = $char;
my $y = 0;
my @origin = ($x, $y);
map {
if ($y < $nlines && $x >= 0) {
my $char = substr($text[$y], $x, 1);
push(@d, $char) if defined $char;
}
$x--;
$y++;
} 0..$nlines-1;
unshift(@d, \@origin);
push(@diagRL, \@d) if @d;
}
for (my $line=1; $line < $nlines; $line++) {
my @d;
$x = $maxlen-1;
my $y = $line;
my @origin = ($x, $y);
map {
if ($y < $nlines && $x >= 0) {
my $char = substr($text[$y], $x, 1);
push(@d, $char) if defined $char;
}
$x--;
$y++;
} 0..$nlines-1;
unshift(@d, \@origin);
push(@diagRL, \@d) if @d;
}
# find text along vertical
for (my $char=0; $char < $maxlen; $char++) {
my @d;
my @origin = ($char, $y);
push(@d, substr($text[$_], $char, 1)) for 0..$nlines-1;
unshift(@d, \@origin);
push(@vertical, \@d);
}
# correct LR to make text greppable
map {
my ($coords, @text) = @$_;
my $text = join('', @text);
$_ = [$text, $coords];
} @diagLR;
# correct RL to make text greppable
map {
my ($coords, @text) = @$_;
my $text = join('', @text);
$_ = [$text, $coords];
} @diagRL;
# correct vertical to make text greppable
map {
my ($coords, @text) = @$_;
my $text = join('', @text);
$_ = [$text, $coords];
} @vertical;
$.diagLR = \@diagLR;
$.diagRL = \@diagRL;
$.vertical = \@vertical;
$.maxlen = $maxlen;
$.nlines = $nlines;
$.text = \@text;
}
# reverse a string
sub _reverse {
my ($self, $text) = @_;
my @text = split //, $text;
return join '', reverse(@text);
}
=head2 B<Grep>
$g2d->Grep($re);
Find the regular expression ($re) no matter where it occurs in
text.
The difference from a regular grep is that "coordinate" information
is returned for matches. This is the length of the
found match, x and y coordinates, along with
directional movement information (dx, dy).
It's easiest to use B<extract> to access matches.
=cut
sub Grep {
my ($self, $re) = @_;
my @matches;
# find things "normally," like a regular grep
push(@matches, ./grep_h($re));
( run in 2.664 seconds using v1.01-cache-2.11-cpan-5837b0d9d2c )