Want regex s/// to replace only nth occurrence

J

jerrykrinock

my $s = "The sneaky cat sneaked sneakily." ;

I would like a simple s/// statement which would replace only the $nth
occurrence of "sneak". ($n is a variable). Can't find the answer.
Is this possible?

Thanks,

Jerry Krinock
 
G

Gunnar Hjalmarsson

my $s = "The sneaky cat sneaked sneakily." ;

I would like a simple s/// statement which would replace only the $nth
occurrence of "sneak". ($n is a variable).

my $repl = 'xyz';
my $n = 2;
my $i;
$s =~ s/(sneak)/ ++$i == $n ? $repl : $1 /eg;
 
D

Dave B

my $s = "The sneaky cat sneaked sneakily." ;

I would like a simple s/// statement which would replace only the $nth
occurrence of "sneak". ($n is a variable). Can't find the answer.
Is this possible?

With sed it's trivial. With Perl, I'd do this:

my $x=0;my $n=3;

while($s=~/sneak/g) {
if (++$x==$n) {
$s=~s/sneak\G/replacement/;
last;
}
}

print "$s\n";

Outputs:

The sneaky cat sneaked replacementily.

I'm a Perl beginner, so there are more idiomatic ways of expressing that, I
guess.
 
M

Mirco Wahab

my $s = "The sneaky cat sneaked sneakily." ;

I would like a simple s/// statement which would replace only the $nth
occurrence of "sneak". ($n is a variable). Can't find the answer.
Is this possible?

It's possible if you use a code assertion (??{...}) to count down
and the atomic group (>...) to prevent backtracking:

my $s = "The sneaky cat sneaked sneakily." ;
our $nth = 3;

$s =~ s/((?>sneak(??{--$nth?'(?!)':''})))/angr/;
print $s;

Regards

M.
 
J

jerrykrinock

Dave, that's the way I would have done if given enough time.

Mirco, you're way over my head. I tested your code and indeed "The
sneaky cat sneaked angrily". But after reading japhy's "Regex
Arcana" (http://japhy.perlmonk.org/articles/tpj/2004-summer.html) for
a few minutes I realized that, being "only an electrical engineer", my
Business Manager would not appreciate the amount of time it would take
me to get up to speed on these amazing "code assertions" and "atomic
groups". But thank you very much for those references. A Google
search for "(??{...})" would not have been very fruitful. If I ever
need these things I've got a bookmark.

Gunnar's solution I was able to understand and modify to do what I
really wanted to do, which is, in DBI database queries, to replace "
= ? " with "IS NULL" when placeholders are undef. This problem is
described in http://search.cpan.org/~timb/DBI-1.605/DBI.pm#Placeholders_and_Bind_Values,
in the subsection "Null Values", but that document doesn't give a
function to test the whole values array and query after construction,
which I could call at a low level in my code to avoid having to
remember to do this all the time. I now have such a function,
explicifyNullsInRefs, shown below.

Thanks for the help!

P.S. There are probably still bugs in this code. Also, probably
someone will point me at a function in a CPAN module that already does
this. Oh, well.

#!/usr/bin/perl

# Problem: A query to find names of all small,
# healthy, 3-year old, albino, fast pets

# Demo Code
my $query = q{SELECT name FROM pets
WHERE
size = ? AND disease = ? AND 'age' = ? and `color`=? and
speed>?} ;
my @values = ("small", undef, 3, undef, 66) ;

print "Query and Values before explifying NULLs:\n" ;
print $query, "\n" ;
printArrayRef(\@values) ;

explicifyNullsInRefs(\$query, \@values) ;
print "\n" ;

print "Query and Values after explifying NULLs:\n" ;
print $query, "\n" ;
printArrayRef(\@values) ;

# Reuseable function
sub explicifyNullsInRefs {
my $queryRef = shift ;
my $valuesRef = shift ;
my $n = " IS NULL" ;
my $e = " = " ;
my $q = "?" ;
my @prunedValues ;

my $i ;
my $k ; # to offset future replacements by current replacment
for my $value (@$valuesRef) {
if (!defined($value)) {
my $j = 0 ;
$$queryRef =~ s/([`'"\w]+)([\s]*)=([\s]*)\?/$j++==$i-$k?
$1.$n:$1.$e.$q/eg ;
$k++ ;
}
else {
push(@prunedValues, $value) ;
}
$i++ ;
}

@$valuesRef = @prunedValues
}

# Function used in demo
sub printArrayRef {
my $aRef = shift ;
my $nA = @$aRef ;
print "$nA values: (" ;
my $i ;
foreach my $v (@$aRef) {
my $printValue = defined($v) ? $v : "<undef>" ;
print $printValue ;
if ($i++ < $nA-1) {
print ", " ;
}
}
print ")\n" ;
}
 
P

Paul Lalli

my $s = "The sneaky cat sneaked sneakily." ;

I would like a simple s/// statement which would replace only the $nth
occurrence of "sneak".   ($n is a variable).  Can't find the answer.
Is this possible?

I'm confused by all the complex answers. Why not something as simple
as this?

my $s = "The sneaky cat sneaked sneakily";
my $n = 3;
my $r = $n - 1;
$s =~ s/((?:sneak.*?){$r})sneak/${1}angr/;


Seems to work fine for $n = 1, 2, and 3 in my tests.

Paul Lalli
 
P

Peter J. Holzer

Gunnar's solution I was able to understand and modify to do what I
really wanted to do, which is, in DBI database queries, to replace "
= ? " with "IS NULL" when placeholders are undef. This problem is
described in http://search.cpan.org/~timb/DBI-1.605/DBI.pm#Placeholders_and_Bind_Values,
in the subsection "Null Values", but that document doesn't give a
function to test the whole values array and query after construction,

I think it does:

| The following technique illustrates qualifying a WHERE clause with
| several columns, whose associated values (defined or undef) are in a
| hash %h:
|
| for my $col ("age", "phone", "email") {
| if (defined $h{$col}) {
| push @sql_qual, "$col = ?";
| push @sql_bind, $h{$col};
| }
| else {
| push @sql_qual, "$col IS NULL";
| }
| }
| $sql_clause = join(" AND ", @sql_qual);
| $sth = $dbh->prepare(qq{
| SELECT fullname FROM people WHERE $sql_clause
| });
| $sth->execute(@sql_bind);

which I could call at a low level in my code to avoid having to
remember to do this all the time. I now have such a function,
explicifyNullsInRefs, shown below.

Thanks for the help!

P.S. There are probably still bugs in this code. Also, probably
someone will point me at a function in a CPAN module that already does
this. Oh, well.

#!/usr/bin/perl

# Problem: A query to find names of all small,
# healthy, 3-year old, albino, fast pets

# Demo Code
my $query = q{SELECT name FROM pets
WHERE
size = ? AND disease = ? AND 'age' = ? and `color`=? and

Note that 'age' = 3 is never true ('age' is a string literal, not a
column name). I assume you mean age without quotes. (`color` is ok, but
non-portable).
speed>?} ;

Having different operators (">" and "=") makes it a bit more
complicated, but one could replace the values of the hash with
[ operator, value ] tuples, like this:
my @values = ("small", undef, 3, undef, 66) ;

# untested code fragment follows:
sub print_pet_names {
my ($dbh, $query) = @_;

for my $col (keys %$query) {
if (defined $query{$col}[1]) {
push @sql_qual, "$col " . $query{$col}[0] . " ?";
push @sql_bind, $h{$col};
}
else {
if ($query{$col}[0] eq '=') {
push @sql_qual, "$col IS NULL";
} else {
push @sql_qual, "$col IS NOT NULL";
}
}
}
$sql_clause = join(" AND ", @sql_qual);
$sth = $dbh->prepare(qq{
SELECT name FROM pets WHERE $sql_clause
});
$sth->execute(@sql_bind);
while (my ($name) = $sth->fetchrow_array) {
print "$name\n";
}
}

print_pet_names($dbh,
{ size => [ '=', 'small' ],
disease => [ '=', undef ],
age => [ '=', 3 ],
color => [ '=', undef ],
speed => [ '>', 66 ],
});

or something similar.

hp
 
M

Mirco Wahab

Paul said:
I'm confused by all the complex answers. Why not something as simple
as this?

my $s = "The sneaky cat sneaked sneakily";
my $n = 3;
my $r = $n - 1;
$s =~ s/((?:sneak.*?){$r})sneak/${1}angr/;

Seems to work fine for $n = 1, 2, and 3 in my tests.

It should work fine until $n = 32765.

BTW yours is faster than I thought:


use strict;
use warnings;
use Benchmark qw(cmpthese);

my $nnn = 10_000 * 3;
my $str = "The sneaky cat sneaked sneakily. hehehe! " x 10_000;


cmpthese(
-1, {
'n-1 Quantifier (Lalli)' => sub {
my ($s, $n) = ($str, $nnn-1);
$s =~ s/((?:sneak.*?){$n})sneak/${1}angr/;
},
'Dynamic Regex (Wahab)' => sub {
my $s=$str; our $n=$nnn;
$s =~ s/(?>sneak(??{--$n?'(?!)':''}))/angr/;
},
}
);



Regards

Mirco
 
C

comp.llang.perl.moderated

With sed it's trivial. With Perl, I'd do this:

my $x=0;my $n=3;

while($s=~/sneak/g) {
if (++$x==$n) {
$s=~s/sneak\G/replacement/;

alternatively:

substr($s, $-[0], $+[0]-$-[0]) = replacement;
 
J

John W. Krahn

comp.llang.perl.moderated said:
With sed it's trivial. With Perl, I'd do this:

my $x=0;my $n=3;

while($s=~/sneak/g) {
if (++$x==$n) {
$s=~s/sneak\G/replacement/;

alternatively:

substr($s, $-[0], $+[0]-$-[0]) = replacement;

alternatively:

substr $s, $-[0], $+[0] - $-[0], 'replacement';


John
 
C

comp.llang.perl.moderated

With sed it's trivial. With Perl, I'd do this:

my $x=0;my $n=3;

while($s=~/sneak/g) {
if (++$x==$n) {
$s=~s/sneak\G/replacement/;

# yet another alternative
substr($s, $-[0], $+[0]-$-[0]) = "...";
last;
}

}

Yet another alternative:
 
D

Dr.Ruud

(e-mail address removed) schreef:
in DBI database queries, to replace "
= ? " with "IS NULL" when placeholders are undef.

Yuck, why would you ever want to do that, and even then: in that ugly
way?

my $query = q{SELECT name FROM pets
WHERE
size = ? AND disease = ? AND 'age' = ? and `color`=? and
speed>?} ;
my @values = ("small", undef, 3, undef, 66) ;

Maybe you can use something like this:

# does this really need that variance in column name quoting?
my $query_f = q{SELECT name FROM pets WHERE size %s AND disease %s AND
'age' %s AND `color` %s};

my $query = sprintf $query_f, map {defined() ? '?' : 'IS NULL'}
@values;
my @bind_values = grep defined(), @values;

(untested)
 
B

Ben Morrow

Quoth (e-mail address removed):
Gunnar's solution I was able to understand and modify to do what I
really wanted to do, which is, in DBI database queries, to replace "
= ? " with "IS NULL" when placeholders are undef.

Try SQL::Abstract.

Ben
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,769
Messages
2,569,580
Members
45,054
Latest member
TrimKetoBoost

Latest Threads

Top