#!/usr/bin/env perl use v5.36; use List::Util qw/min max/; ### Method 1: pyrange() - use Perl slice sub pyrange_slice ( $aref, $start = '', $end = '' ) { # Convert start to a non-negative offset from 0 to @$aref-1 my $offset = !length($start) ? 0 : $start < 0 ? max( @$aref + $start, 0 ) : min( 0 + @$aref, $start ); ### UPDATE was $#$aref # Convert end to a non-negative offset from 0 to @$aref my $beyond = ( !length($end) ? @$aref : $end < 0 ? max( @$aref + $end, 0 ) : min( 0 + @$aref, $end ) ); return [ @$aref[ $offset .. $beyond - 1 ] ]; } ### Method 2: kcott_fixed() - use Perl splice() # Convert [start:stop] to (offset,length) where both offset and length are # non-negative. sub pyrange_splice ( $aref, $start = '', $end = '' ) { my @temp_array = $aref->@*; # Convert start to a non-negative offset from 0 to @$aref-1 my $offset = !length($start) ? 0 : $start < 0 ? max( @$aref + $start, 0 ) : min( 0 + @$aref, $start ); ### UPDATE was $#$aref # Convert end to a non-negative offset from 0 to @$aref my $beyond = !length($end) ? @$aref : $end < 0 ? max( @$aref + $end, 0 ) : min( 0 + @$aref, $end ); # Compute length, zero if range is inverted my $length = max( $beyond - $offset, 0 ); # By now both $offset and $length are non-negative return [ splice @temp_array, $offset, $length ]; } ### Method 3: ask_py() - see what Python does sub ask_py ( $aref, $start = '', $end = '' ) { # Generate python code to evaluate range expression state $py_pat = <<~'EOF'; x=list('%s') # break string into list of chars y=x[%s:%s] # get list range print(''.join(y)) # back to string EOF my $expr = sprintf $py_pat, ( join '', @$aref ), $start, $end; my $r = qx{python3 -c "$expr"}; chomp $r; return [ split //, $r ]; } use constant { AREF => 0, PYTHON => 1, EXP => 2, }; use Test::More; my @test_array = 'a' .. 'k'; my @tests = ( [ \@test_array, '[:3]', 'abc' ], [ \@test_array, '[:-3]', 'abcdefgh' ], [ \@test_array, '[-3:]', 'ijk' ], [ \@test_array, '[3:]', 'defghijk' ], [ \@test_array, '[10:]', 'k' ], [ \@test_array, '[:]', 'abcdefghijk' ], [ \@test_array, '[:0]', '' ], # testing double-ended ranges [ \@test_array, '[-3:-1]', 'ij' ], [ \@test_array, '[-3:-3]', '' ], [ \@test_array, '[-3:0]', '' ], [ \@test_array, '[-3:2]', '' ], [ \@test_array, '[-11:-1]', 'abcdefghij' ], [ \@test_array, '[-11:-0]', '' ], [ \@test_array, '[-12:-1]', 'abcdefghij' ], [ \@test_array, '[-12:1]', 'a' ], [ \@test_array, '[-2:10]', 'j' ], [ \@test_array, '[3:-1]', 'defghij' ], [ \@test_array, '[7:-5]', '' ], [ \@test_array, '[3:4]', 'd' ], [ \@test_array, '[0:12]', 'abcdefghijk' ], ); # Convert python notation to pair of ints now, so we're not timing this # process my $re = qr{^ \[ ( | -? \d+ )? : ( | -? \d+ )? \] $}x; for my $aref (@tests) { my ( $start, $stop ) = $aref->[1] =~ /$re/; $aref->[1] = [ $start, $stop ]; } my @methods = ( [ Perl_slice => \&pyrange_slice ], [ Perl_splice => \&pyrange_splice ], [ Python => \&ask_py ], ); my %methods = map { $_->@* } @methods; my $method; my $c_ref; benchmark(); tester(); # # Benchmarking # sub benchmark() { use Benchmark qw/:all/; cmpthese( -1, { use_slice => sub { run( $methods{Perl_slice} ) }, use_splice => sub { run( $methods{Perl_splice} ) }, } ); } sub run ($c) { $c_ref = $c; for my $test (@tests) { get_array_slice_by_python_expr( $test->@[ AREF, PYTHON ] ); } } # # Testing # sub tester { plan tests => ( 0 + @tests ) * (@methods); for my $aref (@methods) { ( $method, $c_ref ) = @$aref; say "\nTESTING $method with array (" . ( join '', @test_array ) . ')'; for my $test (@tests) { is get_array_slice_by_python_expr( $test->@[ AREF, PYTHON ] ), $test->[EXP], sprintf 'Testing: [%s:%s] expecting (%s)', $test->[PYTHON]->@*, $test->[EXP]; } } } sub get_array_slice_by_python_expr ( $aref, $python ) { my ( $start, $stop ) = @$python; my $range_aref = $c_ref->( $aref, $start, $stop ); return join '', @$range_aref; }