cvs commit: perlfaq perlfaq4.pod

[email protected] (brian d foy) 7 Apr 2005 21:46:37 -0000
Newsgroups perl.cvs.perlfaq
Message-ID <[email protected]>
cvsuser     05/04/07 14:46:36

  Modified:    .        perlfaq4.pod
  Log:
  *  How do I strip blank space from the beginning/end of a string?
  
  I'm replacing this answer completely. The previous answer started
  off with code that it says the user shouldn't use, and the rest
  of the code is a bit more complicated.
  
  I've also added some remarks about preserving blank lines, and
  stripping leading and trailing whitespace in multi-line strings.
  
  * How can I remove duplicate elements from a list or array?
  
  I'm completely replacing this answer. The old answer gave a lot
  of clever answers and used special cases rather than emphasizing
  the trick: use a hash.
  
  Although I show the idiomatic map(), I also through in the more
  newbie-friendly foreach.
  
  Revision  Changes    Path
  1.62      +77 -69    perlfaq/perlfaq4.pod
  
  Index: perlfaq4.pod
  ===================================================================
  RCS file: /cvs/public/perlfaq/perlfaq4.pod,v
  retrieving revision 1.61
  retrieving revision 1.62
  diff -u -r1.61 -r1.62
  --- perlfaq4.pod	11 Mar 2005 16:27:53 -0000	1.61
  +++ perlfaq4.pod	7 Apr 2005 21:46:36 -0000	1.62
  @@ -406,7 +406,7 @@
   
   	use POSIX qw/strftime/;
   	use Time::Local;
  -	my $week_of_year = strftime "%W", 
  +	my $week_of_year = strftime "%W",
   		localtime( timelocal( 0, 0, 0, 18, 11, 1987 ) );
   
   The Date::Calc module provides two functions for to calculate these.
  @@ -422,7 +422,7 @@
       sub get_century    {
   	return int((((localtime(shift || time))[5] + 1999))/100);
       }
  -    
  +
       sub get_millennium {
   	return 1+int((((localtime(shift || time))[5] + 1899))/1000);
       }
  @@ -841,34 +841,52 @@
   
   =head2 How do I strip blank space from the beginning/end of a string?
   
  -Although the simplest approach would seem to be
  +(contributed by brian d foy)
   
  -    $string =~ s/^\s*(.*?)\s*$/$1/;
  +A substitution can do this for you. For a single line, you want to
  +replace all the leading or trailing whitespace with nothing. You
  +can do that with a pair of substitutions.
   
  -not only is this unnecessarily slow and destructive, it also fails with
  -embedded newlines.  It is much faster to do this operation in two steps:
  +	s/^\s+//;
  +	s/\s+$//;
   
  -    $string =~ s/^\s+//;
  -    $string =~ s/\s+$//;
  +You can also write that as a single substitution, although it turns
  +out the combined statement is slower than the separate ones. That
  +might not matter to you, though.
  +
  +	s/^\s+|\s+$//g;
  +
  +In this regular expression, the alternation matches either at the
  +beginning or the end of the string since the anchors have a lower
  +precedence than the alternation. With the C</g> flag, the substitution
  +makes all possible matches, so it gets both. Remember, the trailing
  +newline matches the C<\s+>, and  the C<$> anchor can match to the
  +physical end of the string, so the newline disappears too. Just add
  +the newline to the output, which has the added benefit of preserving
  +"blank" (consisting entirely of whitespace) lines which the C<^\s+>
  +would remove all by itself.
   
  -Or more nicely written as:
  +	while( <> )
  +		{
  +		s/^\s+|\s+$//g;
  +		print "$_\n";
  +		}
   
  -    for ($string) {
  -	s/^\s+//;
  -	s/\s+$//;
  -    }
  +For a multi-line string, you can apply the regular expression
  +to each logical line in the string by adding the C</m> flag (for
  +"multi-line"). With the C</m> flag, the C<$> matches I<before> an
  +embedded newline, so it doesn't remove it. It still removes the
  +newline at the end of the string.
  +
  +    $string =~ s/^\s+|\s+$//gm;
  +
  +Remember that lines consisting entirely of whitespace will disappear,
  +since the first part of the alternation can match the entire string
  +and replace it with nothing. If need to keep embedded blank lines,
  +you have to do a little more work. Instead of matching any whitespace
  +(since that includes a newline), just match the other whitespace.
   
  -This idiom takes advantage of the C<foreach> loop's aliasing
  -behavior to factor out common code.  You can do this
  -on several strings at once, or arrays, or even the
  -values of a hash if you use a slice:
  -
  -    # trim whitespace in the scalar, the array,
  -    # and all the values in the hash
  -    foreach ($scalar, @array, @hash{keys %hash}) {
  -        s/^\s+//;
  -        s/\s+$//;
  -    }
  +	$string =~ s/^[\t\f ]+|[\t\f ]+$//mg;
   
   =head2 How do I pad a string with blanks or pad a number with zeroes?
   
  @@ -1136,56 +1154,46 @@
   
   =head2 How can I remove duplicate elements from a list or array?
   
  -There are several possible ways, depending on whether the array is
  -ordered and whether you wish to preserve the ordering.
  -
  -=over 4
  -
  -=item a)
  -
  -If @in is sorted, and you want @out to be sorted:
  -(this assumes all true values in the array)
  -
  -    $prev = "not equal to $in[0]";
  -    @out = grep($_ ne $prev && ($prev = $_, 1), @in);
  -
  -This is nice in that it doesn't use much extra memory, simulating
  -uniq(1)'s behavior of removing only adjacent duplicates.  The ", 1"
  -guarantees that the expression is true (so that grep picks it up)
  -even if the $_ is 0, "", or undef.
  -
  -=item b)
  -
  -If you don't know whether @in is sorted:
  -
  -    undef %saw;
  -    @out = grep(!$saw{$_}++, @in);
  -
  -=item c)
  -
  -Like (b), but @in contains only small integers:
  -
  -    @out = grep(!$saw[$_]++, @in);
  -
  -=item d)
  -
  -A way to do (b) without any loops or greps:
  +(contributed by brian d foy)
   
  -    undef %saw;
  -    @saw{@in} = ();
  -    @out = sort keys %saw;  # remove sort if undesired
  +Use a hash. When you think the words "unique" or "duplicated", think
  +"hash keys".
   
  -=item e)
  +If you don't care about the order of the elements, you could just
  +create the hash then extract the keys. It's not important how you
  +create that hash: just that you use C<keys> to get the unique
  +elements.
  +
  +   my %hash   = map { $_, 1 } @array;
  +   # or a hash slice: @hash{ @array } = ();
  +   # or a foreach: $hash{$_} = 1 foreach ( @array );
  +
  +   my @unique = keys %hash;
  +
  +You can also go through each element and skip the ones you've seen
  +before. Use a hash to keep track. The first time the loop sees an
  +element, that element has no key in C<%Seen>. The C<next> statement
  +creates the key and immediately uses its value, which is C<undef>, so
  +the loop continues to the C<push> and increments the value for that
  +key. The next time the loop sees that same element, its key exists in
  +the hash I<and> the value for that key is true (since it's not 0 or
  +undef), so the next skips that iteration and the loop goes to the next
  +element.
   
  -Like (d), but @in contains only small positive integers:
  +	my @unique = ();
  +	my %seen   = ();
   
  -    undef @ary;
  -    @ary[@in] = @in;
  -    @out = grep {defined} @ary;
  +	foreach my $elem ( @array )
  +		{
  +		next if $seen{ $elem }++;
  +		push @unique, $elem;
  +		}
   
  -=back
  +You can write this more briefly using a grep, which does the
  +same thing.
   
  -But perhaps you should have been using a hash all along, eh?
  +   my %seen = ();
  +   my @unique = grep { ! $seen{ $_ }++ } @array;
   
   =head2 How can I tell whether a certain element is contained in a list or array?