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

I would like to split the contents of a string from the end to the beginning.

this seemed logical:
my @splits = split(/\./,$string,-2);
but, after checking the manpage on split realized it would not work. So I tried this:
#!/usr/bin/perl -w use strict; my $string = "foo.bar.foobar"; my($val2, $val1) = split(/\./,(reverse $string),2); $val1 = reverse($val1); $val2 = reverse($val2); print "$val1, $val2\n";
The above code works, but it seems there should be a better way. Ideas?

Replies are listed 'Best First'.
Re: backward split
by sauoq (Abbot) on Jun 01, 2003 at 01:15 UTC

    Sometimes there's just no good reason to use split...

    my ($file, $ext) = $string =~ /(.*)\.(.*)/;

    -sauoq
    "My two cents aren't worth a dime.";
    
      ... or a regex (well, directly ;)) ...
      use File::Basename; my @suffix = qw(txt html foobar); my $string = '/path/to/foo.bar.foobar'; my ($file,$path,$ext) = fileparse($string,@suffix);

      jeffa

      L-LL-L--L-LL-L--L-LL-L--
      -R--R-RR-R--R-RR-R--R-RR
      B--B--B--B--B--B--B--B--
      H---H---H---H---H---H---
      (the triplet paradiddle with high-hat)
      
Re: backward split
by BrowserUk (Patriarch) on Jun 01, 2003 at 01:18 UTC

    Seems like reverse split reverse reverse is working a bit too hard.

    my $s = 'foo.bar.foobar'; my ($var1, $var2) = $s =~ m[(^.*)\.(.*?$)]; print $var1, $var2; foo.bar foobar

    Note: The greedy .* in the first capture and the non-greedy .*? in the second.


    Examine what is said, not who speaks.
    "Efficiency is intelligent laziness." -David Dunham
    "When I'm working on a problem, I never think about beauty. I think only how to solve the problem. But when I have finished, if the solution is not beautiful, I know it is wrong." -Richard Buckminster Fuller


      Making the second star nongreedy does not change any of the matches this pattern will find and only causes the regex engine to jump through extra hoops.

      Makeshifts last the longest.

Re: backward split
by Enlil (Parson) on Jun 01, 2003 at 00:35 UTC
    Here is one way:
    #!/usr/bin/perl -w use strict; my $string = "foo.bar.foobar"; my ($val1,$val2) = (split /\./,$string)[-2,-1]; print "$val1,$val2\n";
    update: This returns the last two items in a split, not the item split on the last pattern, as the OP had intended.

    -enlil

      This does not work because it splits the foo.bar portion of the string, which should remain intact according to normal split usage, when split is supplied with a limit.
        Sorry, misinterpreted your intent. This should work to split it that way then:
        #!/usr/bin/perl -w use strict; my $string = "foo.bar.foobar"; my ($val1,$val2) = split /\.(?!.*\.)/,$string; print "$val1,$val2\n";

        update:and another way:

        #!/usr/bin/perl -w use strict; my $string = "foo.bar.foobar"; my $position = rindex($string,'.'); my ($val1,$val2) = ( substr($string,0,$position++), substr($string,$position) ); print "$val1,$val2\n";
        update:Apparently, I suffer from not enough laziness, as in the end would probably have kept thinking there has to be a more elegant solution, and would hopefully stumbled upon sauoq's solution below at some point, which I think is the best solution, but leave the above code if only to show TMTOWTDI.

        -enlil

Re: backward split
by Limbic~Region (Chancellor) on Jun 01, 2003 at 01:00 UTC
    Anonymous Monk,
    Please forgive the poor error checking as this is intended only as an idea of how to accomplish this. It seems that you would want to make this a general (any string, any character to split on, any number of items) re-usable solution, so I offer the following:
    #!/usr/bin/perl -w use strict; my $string = "foo.bar.foobar"; my @array = rev_split($string, '.', 2); print "$_\n" foreach (@array); sub rev_split { return 0 unless (@_ > 1); my ($string , $split , $quantity) = @_; $string = reverse($string); $split =~ /^(.)/; $split = quotemeta $split; my @things = $quantity ? split /$split/ , $string , $quantity : spl +it /$split/ , $string; $_ = reverse($_) foreach(@things); return @things; }

    Cheers - L~R

    Update: I misread and assumed the split should be done first, not the reverse and I have modified code to work as desired.

    Update 2: I realized this isn't as a general solution as I thought since it will only work if the split criteria is a single character. I would be interested in someone coming up with an all purpose reverse split.

Re: backward split
by athomason (Curate) on Jun 01, 2003 at 01:04 UTC
    Since it seems you only want the last few items, you will need the three-argument form of split as you've shown. Putting it all back into one line just requires a map:
    my $string = "foo.bar.foobar"; my $count = 2; my @splits = reverse map {scalar reverse} split(/\./,reverse($string), +$count); # --> @splits = qw/ foo.bar foobar / $string = "foo.bar.foobar.baz.biff"; $count = 3; @splits = reverse map {scalar reverse} split(/\./,reverse($string),$co +unt); # --> @splits = qw/ foo.bar.foobar baz biff /
    That's about as simple as it will get. The scalar forces the string reversal rather than list reversal of each element in the split list. The outermost reverse gives back the original ordering.

    --athomason

Re: backward split
by arthas (Hermit) on Jun 01, 2003 at 13:12 UTC
    Many others already replied you very well offering various different solution. I'll just add that, if you just need to skip the first elements of a string split, you can write as follows:
    # Skips first two elements (undef, undef, $val1, $val2) = split(/\./, $string);
    Michele.
      Of course that assumes you know beforehand how many dots the source string has. :)

      Makeshifts last the longest.

Re: backward split
by edoc (Chaplain) on Jun 01, 2003 at 02:50 UTC

    How about...

    #!/usr/bin/perl -w use strict; my $string = "foo.bar.blah.di.doh"; print "string: $string\n"; my @string; @string = rev_split('.',$string,2); print join(', ',@string)."\n"; @string = rev_split('.',$string,3); print join(', ',@string)."\n"; @string = rev_split('.',$string,4); print join(', ',@string)."\n"; $string = "foo:bar:blah:di:doh"; print "string: $string\n"; @string = rev_split(':',$string,3); print join(', ',@string)."\n"; sub rev_split{ my ($sep,$string,$cnt) = @_; $cnt--; my @string = split(/\Q$sep\E/,$string); $cnt = $#string if $#string < $cnt; my @array = ( join("$sep",@string[0..$#string-$cnt]), (@string[$#str +ing-$cnt+1..$#string]) ); } __END__ prints: string: foo.bar.blah.di.doh foo.bar.blah.di, doh foo.bar.blah, di, doh foo.bar, blah, di, doh string: foo:bar:blah:di:doh foo:bar:blah, di, doh

    Update: modified so join pays attention to the separator..

    Update2: added check to prevent returning more elements than we have.. thanks Enlil

    Of course, with this implementation we can't use a regex as the split separator as join won't know what to join with..

    cheers,

    J