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?