misterperl has asked for the wisdom of the Perl Monks concerning the following question:

Good day Monks!

I want a regex to create a variable number $n instances of a block with access TO the value of n for the nth instance, like if $n==3 , and my block is

cat man
My regex would change it to
1 cat man 2 cat man 3 cat man
like s/(cat\nman\n)/$n $1($n times)/

right now I'm doing this in a tight loop like:

$block .= "$_ cat\nman\n" for 1..$n;

but is there a regex I can use? TY!

Replies are listed 'Best First'.
Re: Can I get some help with a regex please
by hv (Prior) on Jun 13, 2023 at 16:13 UTC

    There is nothing in regexp or substitution syntax that would do this without using additional perl code as in choroba's example. Is there any particular reason you want a regexp for this?

    In normal perl code, you will usually find that joining a bunch of smaller strings in one go is faster than repeatedly appending:

    $block = join '', map "$_ cat\nman\n", 1 .. $n;
Re: Can I get some help with a regex please
by choroba (Cardinal) on Jun 13, 2023 at 15:45 UTC
    #!/usr/bin/perl use warnings; use strict; my $block = "cat\nman\n"; my $n = 3; my $expected = << '__EOF__'; 1 cat man 2 cat man 3 cat man __EOF__ $block =~ s/(cat\nman\n)/join "", map "$_ $1", 1 .. $n/e; use Test::More tests => 1; is $block, $expected, 'same';
    map{substr$_->[0],$_->[1]||0,1}[\*||{},3],[[]],[ref qr-1,-,-1],[{}],[sub{}^*ARGV,3]
      nicely done, but it's none too pretty! I voted you ++ . I was hoping for some built-in DUP functionality on the RHS like \1{$n} or something?
Re: Can I get some help with a regex please
by AnomalousMonk (Archbishop) on Jun 13, 2023 at 17:32 UTC

    The only reason I can imagine for using substitution in a situation like this is if the block of text to be changed is a sub-block within a larger text block:

    c:\@Work\Perl\monks>perl -wMstrict -le "my $str = qq{cat\nman\ncat\ndog\neel\nman\nman\ncat\ncat\nman\n}; ;; my $sub_str = qq{cat\nman\n}; my $n = 3; ;; $str =~ s{ (\Q$sub_str\E) }{ join '', map qq{$_ $1}, 1 .. $n }xmsge; print qq{>$str<}; " >1 cat man 2 cat man 3 cat man cat dog eel man man cat 1 cat man 2 cat man 3 cat man <
    (The \Q...\E metaquoting (update: of the interpolated $sub_str string) is necessary so that newlines are matched literally when the /x modifier is used.)

    Of course, once you have this structure, it's easy to go to full regex search/replace:

    c:\@Work\Perl\monks>perl -wMstrict -le "my $str = qq{cat\nman\ncat\ndog\neel\nman\nman\ncat\ncat\nwoman\n}; ;; my $sub_str = qr{ cat \n (?: wo)? man \n }xms; my $n = 3; ;; $str =~ s{ ($sub_str) }{ join '', map qq{$_ $1}, 1 .. $n }xmsge; print qq{>$str<}; " >1 cat man 2 cat man 3 cat man cat dog eel man man cat 1 cat woman 2 cat woman 3 cat woman <
    (Update: Note that metaquoting is not needed here because $sub_str is a Regexp object: \n matches a literal newline.)


    Give a man a fish:  <%-{-{-{-<

      The question isn't so much about the "reason" I need to do this- more like a better WAY to do it.

      After 30 years of programming Perl, and hours with Larry Wall and Damian and other gurus, my mindset is always CAN I DO THIS BETTER IN A REGEX?

      Reluctantly , I sometimes have to capitulate and conclude NO, there is no "better regex approach". This MIGHT be that. The tite loop I showed was the shortest and clearest solution so far.

      The real question is,

      are there RHS "tricks" I can do with $1 $2 ... ? like a way to repeat $1 $n times? And a way to know what iteration each is?
      It would seem "perlish" to have this capacity. Like:
      s/(block)/$1{$n}/
      where $1 is repeated $n times.. Im not sure about a way to know which iteration its on- maybe a reserved PerlVar?

        In any sort of production code, i would go so far as to recommend a clearer if slower classic C-style for loop. Yes, it's not optimized, but it is maintainable by someone who isn't a perl expert:

        my $result = ''; for(my $i = 0; $i < $n; $i++) { $result .= "$i cat\n\man\n"; }

        Or a slightly different variant that would lead itself to easier debugging:

        my @lines; for(my $i = 0; $i < $n; $i++) { push @lines, "$i cat"; push @lines, "man"; } #print Dumper(\@lines); my $result = join("\n", @lines);

        Unless it's really timing critical code (in which case thinking about doing the stuff in XS/Inline::C is also an option), long term maintainability and readability is (almost) always more important than saving a couple percent CPU cycles.

        So the answer, at least from my point of view: Can you do it faster with a regular expression: Maybe. Can you do it better? Not a chance, unless you add a few long paragraphs of explanations in the code comments that clearly lays out how to chance/update the code in a few years to fit the changing requirements of the project.

        PerlMonks XP is useless? Not anymore: XPD - Do more with your PerlMonks XP
Re: Can I get some help with a regex please
by ikegami (Patriarch) on Jun 13, 2023 at 18:13 UTC

    A regex pattern defines a set of strings. In theory, it would be possible to generate the set of strings a regex pattern defines, achieving what you want. There's no means to do that in core Perl, but there might a module to do this.

    Update: No, there's really simple pattern that define a set containing just the desired string. I was thinking of something like qr/^(?:cat\nman\n){$n}\z/, but that's not correct.

    I'm not sure if it exists as a module because it's not really a solution to any problem. Regex weren't designed to compose strings. With that in mind, I'll leave you with the following two alternatives:

    Silly regex-based solution:

    join "", map s/\z/ cat\nman\n/r, 1..$n

    But that's just an expensive version of

    join "", map "$_ cat\nman\n", 1..$n