tags:

views:

2668

answers:

8

How do I make an array shorter in Perl? I read some webpages indicating that I can assign:

$#ARRAY = 42;

I read that the use of $# is deprecated. I need a solution that will work for an array of arrays, too. This didn't work:

$#$ARRAY[$i] = 42;
A: 

$#{$ARRAY[$i]} = 42;

moonshadow
A: 

You could do

splice @array, $length;
#or
splice @{$arrays[$i]}, $length;
Leon Timmermans
You have an spurious dot in your second example.
Michael Carman
+6  A: 

Your options are near limitless (I've outlined five approaches here) but your strategy will be dictated by exactly what your specific needs and goals are. (all examples will convert @array to have no more than $N elements)


[EDIT]

As others have pointed out, the way suggested in the original question is actually not deprecated, and it provides the fastest, tersest, but not necessarily the most readable solution. It also has the side effect of expanding an array of fewer than $N elements with empty elements:

$#array = $N-1;


Least code:

#best for trimming down large arrays into small arrays
@array = $array[0..($N-1)];

Most efficient for trimming a small number off of a large array:

#This is a little less expensive and clearer
splice(@array, $n, @#array);

Undesirable in almost all cases, unless you really love delete():

#this is the worst solution yet because it requires resizing after the delete
while($N-1 < $#array)
{
   delete(array[$i]);
}

Useful if you need the remainder of the list in reverse order:

#this is better than deleting because there is no resize
while($N-1 < $#array)
{
    pop @array;
    #or, "push $array2, pop @array;" for the reverse order remainder
}

Useful for saving time in long run:

#don't put more values into the array than you actually want
Frosty
That scales pretty badly, specially when you're having a lot of elements in your array but only remove few.
Leon Timmermans
Good point. I expanded the original answer to include that caveat and to provide a few other options.
Frosty
+9  A: 

I'm not aware of assigning $#ARRAY being deprecated; perldoc perldata from 5.10.0 certainly says nothing about it. It is the fastest way to truncate an array.

If you want something a little more readable, use splice:

splice @ARRAY, 43;

(Note 43 instead of 42 - $#ARRAY gets you the last index of the array, whereas splice taks the length of the array instead).

As for working on arrays of arrays, I assume you mean being able to truncate a nested array via a reference? In that case, you want:

$#{$ARRAY->[7]} = 42;

or

splice @{$ARRAY->[7]}, 43;
Robert Norris
+1  A: 
  • $#array is the last index of the array.
  • $#$array would be the last index of an array pointed at by $array.
  • $#$array[$i] means you're trying to index a scalar--can't be done. $#{$array[3]} properly resolves the subscripting of the main array before we try to reference the last index.
  • Used alone

    $#{$array[3]} = 9;

    assigns a length of 9 to the autovivified array at $array[3].

  • When in doubt, use Data::Dumper:

    use Data::Dumper;
    $#{$array[3]} = 5;
    $#array       = 10;
    print Dumper( \@array, $array ), "\n";
    
Axeman
+4  A: 

You essentially gave the canonical answer yourself. You shorten an array by setting the last index:

$#Array = 42

The $#Foo notation for denoting the last index in the array is absolutely not deprecated. Similarly, assigning to it will not be deprecated either. Quoting the perldata documentation:

The length of an array is a scalar value. You may find the length of array @days by evaluating $#days, as in csh. However, this isn’t the length of the array; it’s the subscript of the last element, which is a different value since there is ordinarily a 0th element. Assigning to $#days actually changes the length of the array. Shortening an array this way destroys intervening values. Lengthening an array that was previously shortened does not recover values that were in those elements. (It used to do so in Perl 4, but we had to break this to make sure destructors were called when expected.)

tsee
+7  A: 

The $# variable is deprecated, but the $#array feature is not.

To use the $#array syntax on an arbitrary expression that yields an array reference, do $#{ EXPR }.

See the invaluable: http://perlmonks.org/?node=References+quick+reference

ysth
A: 

There are two ways of interpreting the question.

  • How to reduce the length of the array?
  • How to reduce the amount of memory consumed by the array?

Most of the answers so far focus on the former. In my view, the best answer to that is the splice function. For example, to remove 10 elements from the end:

splice @array, -10;

However, because of how Perl manages memory for arrays, the only way to ensure that an array takes less memory is to copy it to a new array (and let the memory of the old array be reclaimed). For this, I would tend to think about using a slice operation. E.g., to remove 10 elements:

@new = @old[ 0 .. $#old - 10 ]

Here's a comparison of different approaches for a 500 element array (using 2104 bytes):

  original: length  500 => size 2104
     pound: length  490 => size 2208
    splice: length  490 => size 2104
    delete: length  490 => size 2104
     slice: length  490 => size 2064

You can see that only the slice operation (copied to a new array) has a smaller size than the original.

Here's the code I used for this analysis:

use strict;
use warnings;
use 5.010;
use Devel::Size qw/size/;

my @original = (1 .. 500);
show( 'original', \@original );

my @pound = @original;
$#pound = $#pound - 10;
show( 'pound', \@pound );

my @splice = @original;
splice(@splice,-10);
show( 'splice', \@splice);

my @delete = @original;
delete @delete[ -10 .. -1 ];
show( 'delete', \@delete );

my @slice = @original[0 .. $#original - 10];
show( 'slice', \@slice);

sub show {
    my ($name, $ref) = @_;
    printf( "%10s: length %4d => size %d\n", $name, scalar @$ref, size($ref));
}
xdg