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

I needed a function with the following properties: prefix_print(@list) should work exactly like print(@list), except that each line in the output should be prefixed by $prefix. For example, if $prefix is set to 'P', a call to prefix_print("A\nB","C\n") should print:
PA PBC

My first attempt went like this:

sub prefix_print { # Put $prefix in front my $outstr=join('',$prefix,@_); # If something follows a \n, insert $prefix $outstr =~ s/(\n)(?=.)/$1.$prefix/egs; print $outstr; }
This code works, as far I can see, but it requires the usage of a temporary variable, $outstr. Of course this is not really a problem, but being very fond of Perl's functional aspects (for instance, using split, map and so on), and - more out of curiosity than necessity - I was searching for an alternative solution, working without temporary variable and still simple enought to be understandable. Here is my attempt:
# Note: This is buggy sub prefix_print { # Turn arguments into a list of lines, and prepend # each with $prefix print( map { "$prefix$_" } join('',@_) =~ m/(.*(?:\n|$))/g ); }
This does NOT work, however, because if called as prefix_print('y'), it prints
PyPP
which suggests that my regular expression returns an array of three matches (one containing 'y', and two containing the empty string).

Could someone explain where these matches come from? Also, I would be glad to learn about any improvements for my code.

-- 
Ronald Fischer <ynnor@mm.st>

Replies are listed 'Best First'.
Re: Can we do without auxiliary variable here?
by Corion (Patriarch) on Feb 28, 2011 at 16:23 UTC

    I'm not sure that extracting things is the right approach. Instead I'd try to replace all newlines with \n$prefix (resp. all "starts" of the string with the prefix, in REspeak):

    sub prefix_print { # unfortunately, the local copy of $_ is necessary to be able to p +refix_print # constants print map { my $l = $_; $l =~ s/^/$prefix/gm; $l } @_ }; prefix_print qq(foo\nbar),2,3,4
Re: Can we do without auxiliary variable here?
by ig (Vicar) on Feb 28, 2011 at 17:58 UTC

    What do you want from the following?

    prefix_print("A\nB"); prefix_print("C\n");

    Update: I might do something like the following:

      Good catch. I imagine a "\n" would need to be added.
Re: Can we do without auxiliary variable here?
by Eliya (Vicar) on Feb 28, 2011 at 16:59 UTC

    Another one:

    sub prefix_print { print map $_ ne "\n" ? "$prefix$_":$_, split /(\n)/, join '',@_; }

    The split /(\n)/ also returns the newlines, so you can prefix anything that isn't a newline, while passing the newlines through as is.  This way, you don't have to take care of boundary cases like trailing newline and initial prefix.

Re: Can we do without auxiliary variable here?
by ikegami (Patriarch) on Feb 28, 2011 at 20:32 UTC

    Some clear solutions:

    my $s = join('', @_); $s =~ s/^/$prefix/mg; print $s;
    print map { $prefix . $_ } split /^/m, join('', @_);
    print apply { s/^/$prefix/mg } join('', @_);
    print join('', @_) =~ s/^/$prefix/rmg;

    apply comes from List::MoreUtils

    The last requires Perl 5.14 for /r.

Re: Can we do without auxiliary variable here?
by locked_user sundialsvc4 (Abbot) on Feb 28, 2011 at 20:52 UTC

    Being an old crotchety person by now, I take the Clark Gable Approach™ to such things:   “Frankly, my dear, I don’t give a damn”™ about how you write it, as long as what you write is abundantly clear.   The original version of this code was, indeed, clear.   (Therefore:   priceless.)   Save the rest for “Obfuscation” and “Poetry.”

    Q:   Can we do without an auxiliary variable here?
    A:   Clark Gable.™

      Right. The only reason to change clear code I see is a massive performance problem in the respective code, typically shown by running a profiler. Everything else is premature optimization and thus evilTM.

      Alexander

      --
      Today I will gladly share my knowledge and experience, for there are no sweeter words than "I told you so". ;-)
      This is an interesting aspect, because in the end, I found ikegami's solution using map in combination with /^/ easier to understand than my original one, once I understood it. Before this, I was not aware that matching on /^/ could make sense (Perl's universe of regular expression exceeds my current knowledge by far). Probably that's typical for readability questions: Readability depends also very much on the experience of the reader...

      -- 
      Ronald Fischer <ynnor@mm.st>
Re: Can we do without auxiliary variable here?
by Anonymous Monk on Feb 28, 2011 at 16:18 UTC
    print map { "$prefix$_" } split /\n/, join '', @_
    print map { "$prefix$_" } join('',@_) =~ m/([^\n]+\n?)/g

      print map { "$prefix$_" } split /\n/, join '', @_

      Fixed:

      print map { "$prefix$_" } split /^/m, join('', @_), -1;

      Note: split implies the "m" for that pattern, but it's clearer to specify it.

      Update: As Eliya kinda points out, there won't ever be null trailing fields with that pattern, so the above can be simplified to

      print map { "$prefix$_" } split /^/m, join '', @_;
        Great solution! I completely forgot about the "negativ LIMIT" parameter of split.

        -- 
        Ronald Fischer <ynnor@mm.st>
      Compact solution, but unfortunately doesn't fulfil the specification:

      The first solution is not correct, because it would drop the newline characters. The second solution keeps most newline characters, but if the string contains a sequence of \n, all but the first are ignored...

      -- 
      Ronald Fischer <ynnor@mm.st>
Re: Can we do without auxiliary variable here?
by roboticus (Chancellor) on Feb 28, 2011 at 18:10 UTC

    rovf:

    I rather like:

    #!/usr/bin/perl use warnings; use strict; my $prefix="prefix: "; sub printpre { unshift @_, $prefix; print @_; } printpre "apple ", "banana ", "tomfoolery ", "\n";

    ...roboticus

    When your only tool is a hammer, all problems look like your thumb.

      Why not write that without the auxiliary array manipulation there?

        chromatic:

        Ha, ha! It never occurred to me!

        #!/usr/bin/perl use strict; use warnings; my $prefix="Prefix: "; sub print_prefix { print $prefix, @_; } print_prefix "alpha ", "beta ", "gamma\n";

        Sigh. When I read your reply, I thought, "Hmm, does unshift return a reference to the array?", so a first I thought you were prompting me to do something like:

        print unshift @_, $prefix;

        But when I checked the docs, I could see that it wouldn't fly. Then I understood what you meant. Ah, well! I'm always amused at how my mind works: it seems I frequently complicate simple things. ;^)

        ...roboticus

        When your only tool is a hammer, all problems look like your thumb.

      It seems to me the OP has a logger and he wants to include the prefix at the start of every line, not the start of every message. Those aren't the same if there's a possibility of multi-line messages.

      This would not cope when there are "\n" in between. See the comment by ikegami.

      -- 
      Ronald Fischer <ynnor@mm.st>

        rovf:

        Yeah, I clearly missed on that one. I then tried:

        sub print_prefix { local $/="\nPrefix: "; print @_; }

        But that didn't work. On first guess, I thought it would do OK. After reading the documentation, I thought it would print it at least once. But what I got was...nothing. It never printed prefix at all. The only other methods I could think of had already been covered.

        ...roboticus

        When your only tool is a hammer, all problems look like your thumb.

Re: Can we do without auxiliary variable here?
by ig (Vicar) on Feb 28, 2011 at 19:47 UTC
    Could someone explain where these matches come from?

    Calling your second version with prefix_print('y'), I get "PyP", not "PyPP". I am testing with perl 5.12.2, default compile from source on linux.

    The match returns two matches on my system:

    1. the 'y' at the end of the line
    2. then '' (.* can match nothing) at the end of the line

    It would be an infinite loop (forever matching nothing at the end of the line) except for Repeated Patterns Matching a Zero length Substring

    I can't imagine how you got "PyPP"