简体   繁体   中英

How can I golf this Perl subroutine that does a substitution?

I have the following subroutine in Perl to substitute "abc" for "xyz" in a string:

sub mySubst {
    my ($str) = @_;
    $str =~ s|abc|xyz|ig;
    return $str;    
}

It works, but seems way too verbose for Perl. How can I tighten it up?

What you have is fine.

  • You pull the arguments off the @_ variable, making a copy, using a list assignment. List assignment is an excellent way to do it (and basically the standard way.) Using shift would also work, but changes @_ (may or may not be what you want.) There's a discussion on PerlMonks about shift vs @_ that you might be interested in.
  • You use a named variable with your search and replace. I prefer named variables in this case, since dealing with Perl's magic variables takes care.
  • Automatically working on $_ would be possible, but not having $_ auto populated makes it trickier to get right. You'd need to do local $_ = shift; or local ($_) = @_; which doesn't add much.)
  • I like it when people use an explicit return . It's a warm fuzzy.
  • K&R Brackets. Good. :)
  • No prototype. Very good. :)

Go with it. I think you're on the right track.

If you're looking for golf, and not production code.

use strict;   ## Not really required ;)
use warnings; ## Not really required ;)
sub f{local$_=pop,s/foo/bar/ig;$_}
print f 'foobarbaz';

You could write:

sub mySubst { (map { s|abc|xyz|ig; $_ } "$_[0]" )[0] }

but unless this is an exercise in obfuscation, I would say go with what you have. Remember, you are not writing the program for the computer.

This is how I would render that subroutine more idiomatically:

sub mySubst {
    (my $str = shift) =~ s|abc|xyz|ig;
    return $str;
}

I think too verbose sounds like not enough obfuscation which I disagree. As for tightening it up I'd suggest something along the lines of:

sub replaceBeginningWithEnd {
    my $text = shift;
    return if not defined($text);
    $text =~ s/abc/xyz/ig;
    return $text;    
}
  • Make names more readable
  • Use conventions (ie. / as oppose to |)
  • Argument checking

No one gave the Perl idiom for this yet. This replicates the behavior of the original code by modifiying a copy:

  (my $new_str = $old_str) =~ s/abc/xyz/ig;

If I had to put that into a subroutine, which I think is kinda silly for such a simple operation, I guess that would be:

 sub foo { (my $s = $_[0]) =~ s/abc/xyz/ig; $s }

However, if I was doing something silly like this, I wouldn't make a named subroutine for it. I'd make a subroutine to make the subroutine for me so I don't need a new named sub for every possible sort of replacement:

 sub make_sub {
      my( $regex, $replacement ) = @_;

      # season to taste with error checking
      sub {
          (my $s = $_[0]) =~ s/$regex/$replacement/ig; $s
          };
      }

 my $replacer = make_sub( qr/abc/, 'xyz' );

 my $new = $replacer->( $string );

what is the sub for? just do it like this

$str =~ s|abc|xyz|ig;

You can omit the return keyword:

sub mySubst {
    my ($str) = @_;
    $str =~ s|abc|xyz|ig;
    $str;    
}

It is also possible to use the default variable ( $_ ) :

sub mySubst {
    local $_ = shift;  # or my starting from Perl 5.10
    s|abc|xyz|ig;
    $_;    
}

To avoid changing the originals, you could write

sub mySubst { map { (my $s=$_) =~ s|abc|xyz|ig; $s } @_ }

or

sub mySubst { my @a = @_; map { s|abc|xyz|ig; $_ } @a }

or to borrow from Sinan's answer, except discarding the temporary array explicitly:

sub mySubst { map { s|abc|xyz|ig; $_ } my(undef) = @_ }

but the syntax is still a little noisy. Because it uses map , be sure to call it in list context:

my($result) = mySubst $str;  # NOT my $one = mySubst $str;

If you expect to mostly call mySubst with a single argument but want to handle cases of one or more arguments, then you could write

sub mySubst {
  s|abc|xyz|ig for my @a = @_;
  wantarray ? @a : $a[0];
}

but that starts up the stuttering again.


If you want to update the parameter itself, use the alias semantics of Perl's subs as documented in perlsub :

The array @_ is a local array, but its elements are aliases for the actual scalar parameters. In particular, if an element $_[0] is updated, the corresponding argument is updated (or an error occurs if it is not updatable).

So you could write

sub mySubst { $_[0] =~ s|abc|xyz|ig }

or even

sub mySubst { map { s|abc|xyz|ig; $_ } @_ }

Example usage:

$str = "fooabcbar";
mySubst $str;
print $str, "\n";

Output:

fooxyzbar

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM