perlpdl

Splitting a PDL in half


I have a one-dimensional PDL that I'd like to perform calculations on each half of; i.e. split it, then do calculations on the first half, and the same calculations on the second half.

Is there an easier/nicer/elegant way to simply split the PDL in half than getting the number of elements (with nelem), dividing that in two, then doing two lots of slices?

Thanks


Solution

  • Yes, in so far as you don't need to directly invoke slice to get what you want. You could chain splitdim and dog with something like this:

    # Assume we have $data, a piddle
    my ($left, $right) = $data->splitdim(0, $data->nelem/2)->dog;
    

    That, of course, is easily extended to more than two divisions. However, if you want to extend it to higher-dimensional piddles (i.e. a collection of time series all stored in one piddle), you would need to be a little more subtle. If you want to split along the first dimension (which has index 0), you would say this instead:

    # Assume we have $data, a piddle
    my ($left, $right) = $data->splitdim(0, $data->dim(0)/2)->mv(1, -1)->dog;
    

    The splitdim operation splits the 0th dimension into two dimensions, the 0th being dim(0)/2 in length, the 1st being 2 in length (because we divided it into two pieces). Since dog operates on the last dimension, we move the 1st dimension to the end before invoking dog.

    However, even with the single-dimensional solution, there's a caveat. Due to the way that $data->splitdim works, it will truncate the last piece of data if you have an odd number of elements. Try that operation on a piddle with 21 elements and you'll see what I mean:

    my $data = sequence(20);
    say "data is $data";  # lists 0-19
    my ($left, $right) = $data->splitdim(0, $data->nelem/2)->dog;
    say "left is $left and right is $right";  # lists 0-9, then 10-19
    
    $data = sequence(21);
    say "data is $data";  # lists 0-20, i.e. 21 elements
    my ($left, $right) = $data->splitdim(0, $data->nelem/2)->dog;
    say "left is $left and right is $right";  # lists 0-9, then 10-19!!
    

    If you want to avoid that, you can produce your own method that splits the first dimension in half without truncation. It would probably look something like this:

    sub PDL::split_in_half {
        my $self = shift;
    
        # the int() isn't strictly necessary, but should make things a
        # tad faster
        my $left = $self->slice(':' . int($self->dim(0)/2-1) );
        my $right = $self->slice(int($self->dim(0)/2) . ':');
        return ($left, $right);
    }
    

    Here I have also used the int built-in to make sure we don't have the .5 if dim(0) is odd. It's a little more complicated, but we're burying this complexity into a method precisely so we don't have to think about the complexity, so we may as well buy ourselves a few clock cycles while we're at it.

    Then you could easily invoke the method thus:

    my ($left, $right) = $data->split_in_half;