Want regex s/// to replace only nth occurrence

Discussion in 'Perl Misc' started by jerrykrinock@gmail.com, Jul 6, 2008.

  1. Guest

    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
     
    , Jul 6, 2008
    #1
    1. Advertising

  2. wrote:
    > 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;

    --
    Gunnar Hjalmarsson
    Email: http://www.gunnar.cc/cgi-bin/contact.pl
     
    Gunnar Hjalmarsson, Jul 6, 2008
    #2
    1. Advertising

  3. Dave B Guest

    wrote:

    > 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.

    --
    D.
     
    Dave B, Jul 6, 2008
    #3
  4. Mirco Wahab Guest

    wrote:
    > 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.
     
    Mirco Wahab, Jul 6, 2008
    #4
  5. Guest

    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" ;
    }
     
    , Jul 6, 2008
    #5
  6. Paul Lalli Guest

    On Jul 6, 9:17 am, wrote:
    > 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
     
    Paul Lalli, Jul 7, 2008
    #6
  7. On 2008-07-06 21:39, <> wrote:
    > 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
     
    Peter J. Holzer, Jul 7, 2008
    #7
  8. Mirco Wahab Guest

    Paul Lalli wrote:
    > 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
     
    Mirco Wahab, Jul 7, 2008
    #8
  9. On Jul 6, 6:58 am, Dave B <> wrote:
    > wrote:
    > > 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/;


    alternatively:

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

    > last;
    > }
    >
    > }
    >

    --
    Charles DeRykus
     
    comp.llang.perl.moderated, Jul 8, 2008
    #9
  10. comp.llang.perl.moderated wrote:
    > On Jul 6, 6:58 am, Dave B <> wrote:
    >> wrote:
    >>> 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/;

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


    alternatively:

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

    >> last;
    >> }
    >>
    >> }



    John
    --
    Perl isn't a toolbox, but a small machine shop where you
    can special-order certain sorts of tools at low cost and
    in short order. -- Larry Wall
     
    John W. Krahn, Jul 8, 2008
    #10
  11. On Jul 6, 6:58 am, Dave B <> wrote:
    > wrote:
    > > 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/;


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

    > last;
    > }
    >
    > }


    Yet another alternative:
     
    comp.llang.perl.moderated, Jul 8, 2008
    #11
  12. Dr.Ruud Guest

    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)

    --
    Affijn, Ruud

    "Gewoon is een tijger."
     
    Dr.Ruud, Jul 9, 2008
    #12
  13. Ben Morrow Guest

    Quoth :
    >
    > 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

    --
    We do not stop playing because we grow old;
    we grow old because we stop playing.
     
    Ben Morrow, Jul 9, 2008
    #13
    1. Advertising

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

It takes just 2 minutes to sign up (and it's free!). Just click the sign up button to choose a username and then you can ask your own questions on the forum.
Similar Threads
  1. Code4u
    Replies:
    4
    Views:
    2,647
    Stephen Howe
    Jul 13, 2005
  2. TP
    Replies:
    5
    Views:
    1,405
  3. Ross
    Replies:
    15
    Views:
    295
    John W. Kennedy
    Jul 7, 2005
  4. PerlFAQ Server
    Replies:
    0
    Views:
    114
    PerlFAQ Server
    Jan 12, 2011
  5. PerlFAQ Server
    Replies:
    0
    Views:
    132
    PerlFAQ Server
    Feb 18, 2011
Loading...

Share This Page