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

Dear Monks, I am having trouble finding the most efficient way to complete the following task. I have an array @array with entries (x, x, y, z, z, z) and I want to grab the indices of the array that correspond to the first and last of each identical value. Thus, for this example I want to make an array that has the values (0, 1, 2, 2, 3, 5). Any help would be greatly appreciated.

Replies are listed 'Best First'.
Re: Grabbing slices
by bart (Canon) on May 03, 2003 at 20:14 UTC
    Does the result have to be that obscure kind of array? Here's something you could play with:
    @array = ('x', 'x', 'y', 'z', 'z', 'z'); my %items; for my $i (0 .. $#array) { $items{$array[$i]} ||= [ $i, $i ]; # only assigns for the first o +ccurrence $items{$array[$i]}[1] = $i; # replaces for every new occur +rence } use Data::Dumper; print Dumper \%items;
    which produces:
    $VAR1 = { 'x' => [ 0, 1 ], 'y' => [ 2, 2 ], 'z' => [ 3, 5 ] };
    So it fills a hash of items, with an array with 2 entries for each: the first occurrence with index 0, and the last, index 1.

    The tricks it uses to achieve this, is using ||=, which only assigns at the first occurrence, provided that what it assigns is always true; and = which always replaces what was there already, in the end leaving it in the state of the last assignment.

    In case you have multiple ranges for the same item, it may or may not do what you want.

Re: Grabbing slices
by BrowserUk (Patriarch) on May 03, 2003 at 19:19 UTC

    #! perl -slw use strict; my @array = qw[ x x y z z z ]; my @idxs = 0; $array[$_-1] ne $array[$_] and push @idxs, $_-1, $_ for 1 .. $#array; push @idxs, $#array; print "@idxs";

    Examine what is said, not who speaks.
    1) When a distinguished but elderly scientist states that something is possible, he is almost certainly right. When he states that something is impossible, he is very probably wrong.
    2) The only way of discovering the limits of the possible is to venture a little way past them into the impossible
    3) Any sufficiently advanced technology is indistinguishable from magic.
    Arthur C. Clarke.
      Let me see if I can expand this correctly...
      my @idxs = (0); my $i; foreach $i (1 .. $#array) { if ($array[$i - 1] ne $array[$i]) { push @idxs, $i - 1, $i; } } push @idxs, $#array; print "@idxs";
Re: Grabbing slices
by pzbagel (Chaplain) on May 03, 2003 at 19:01 UTC

    Well, if there is a perl-ish way to do it, I just don't know how to do it. Here is the simple loop over array while storing the start and end of each char way:

    @array=qw(x x y z z z); $c=$array[0]; $start=0; $end=0; for $index (1..$#array){ if (!($c eq $array[$index])){ push(@output, $start, $end); $c=$array[$index]; $start=$index; $end=$index; }else{ $end=$index; } } push(@output, $start, $end); print join(" ", @output), "\n"; Outputs: 0 1 2 2 3 5

    I'm sure there someone else can probably do better.

    Cheers

Re: Grabbing slices
by dbush (Deacon) on May 03, 2003 at 19:03 UTC

    The following seems to do what you want. Not sure if this is the most efficient approach though.

    use strict; use warnings; use Data::Dumper; my (@array, $iIndex, $thisValue, $lastValue, @out); @array = qw(x x y z z z); @out = (0); $lastValue = $array[0]; for ($iIndex = 0; $iIndex <= $#array; $iIndex++) { $thisValue = $array[$iIndex]; if ($thisValue ne $lastValue) { push @out, ($iIndex - 1, $iIndex); $lastValue = $thisValue; } } push @out, $#array; print Dumper(\@out);

    Regards,
    Dom.

      Actually, now I come to look at the output from dumper:

      $VAR1 = [ 0, '1', 2, '2', 3, 5 ];

      I'm wondering about the significance of some of the numbers coming out in quotes. At first I wondered if it had something to do with the brackets on the push line.

      Can anyone enlighten me?

      Cheers,
      Dom.

        It is a bug in the XS version of Data::Dumper before v2.11. To see your version id:

        perl -MData::Dumper -wle 'print $Data::Dumper::VERSION'
        2.102 is in the base code for Perl 5.6.1, and has the bug.
        2.12 is in the base code for Perl 5.8.0, and does not have the bug.

        My testing script:

        perl -MData::Dumper -wle '$Data::Dumper::Indent=0;print Dumper [ map { + ($_, $_+0) } 0..3 ]'
        Good output:
        $VAR1 = [0,0,1,1,2,2,3,3];
        Buggy output:
        $VAR1 = [0,'0',1,'1',2,'2',3,'3'];

        If you set Useqq, as a side-effect, it will force Data::Dumper to use the pure-Perl forms of its code.
        This is slower, but will work around the bug in older versions.

        $Data::Dumper::Useqq = 1;