How to get neighboring values at the given offset of an array?

Asked

Viewed 97 times

0

I’m having trouble extracting a portion of any sequentially indexed array, such as a single crease().

The problem is that I need to specify a starting offset and a limit greater than or equal to 1. This limit would control how many elements would be searched before and after a given offset, besides itself, of course. For example:

$letras = range( 'A', 'Z' );
$offset = 3;
$limite = 2;

In this case the routine should return the fourth offset (D), two ahead (E and F) and two behind (B and C).

Already if those values changed to, who knows:

$offset = 10;
$limite = 3;

The same routine should return the eleventh index (K), three ahead (L, M and N) and three back (H, I and J).

Well, initially I resorted to array_slice() believing it to be the right tool for the job. However using it proved more difficult than it seemed because the third argument of the function works with lengths and not with offsets:

$collection = range( 'A', 'Z' );
$offset     = 10;
$limit      = 3;

$length = count( $collection );

if( $offset >= $length ) {
    throw new \OutOfRangeException( 'Requested offset exceeds the size of Collection' );
}

$start = ( $offset - $limit ) >= 0 ? ( $offset - $limit ) : 0;
$end   = $limit + 2;

$slice = array_slice( $collection, $start, $end );

I imagine what you’re looking for is a mathematical solution but I’m not sure how to make the logic.

Can someone help me?

A very important additional information is that the solution, whatever it is, also does not lean on those "witchcraft" that we see, for example, reversing the keys array with their values. This is because here, in this example, I am demonstrating with a simple array filled with letters but the actual use will be a Collection of Objects, for example:

$std1 = new\stdClass;
$std1 -> name = 'Name #1';

$std2 = new\stdClass;
$std2 -> name = 'Name #2';

$std3 = new\stdClass;
$std3 -> name = 'Name #3';

$std4 = new\stdClass;
$std4 -> name = 'Name #4';

$std5 = new\stdClass;
$std5 -> name = 'Name #5';

$collection = [ $std1, $std2, $std3, $std4, $std5 ];

But, logical and evident that not mere Objects stdClass, nor Objects that already have some interface implemented, although all my Objects extend from the same base class for other reasons unrelated to this topic.

2 answers

1

You can use your own array_slice that mentioned or could use a simple for, I think the latter would be easier, could also use next and prev, finally has several options.

function selecionar($array, $comeco, $limite){

    $comeco  -= $limite + 1;
    $limite   = $limite * 2 + 1;

    for($limite; $limite > 0; $limite--){

        if(isset($array[$comeco + $limite])){
            $selecao[] = $array[$comeco + $limite];
        }

    }

    return $selecao;

}

Logo, using:

echo implode(',', selecionar($collection, 3, 2));

Will return exactly F,E,D,C,B.


Using the array_slice without any correction (if the limit is greater than the offset will have problems, for example), could use:

array_slice($collection, $offset - $limit, $limit * 2 + 1);

Soon:

$collection = range( 'A', 'Z' );
$offset = 3;
$limit = 2;

echo implode(',',  array_slice($collection, $offset - $limit, $limit * 2 + 1));

It would result exactly in B,C,D,E,F.

  • Your iterating solution worked, reverse order m of course, but it worked. But I really wanted to try everything possible to avoid a loop. The second, which by the way I had imagined and tried but omitted from the original post, did not work for very small offsets. Ex: $offset = 0 and $limite = 2 causes $offset - $limit negative and array_slice() take from the end.

1

I imagine if you do $offset - start + $limit + 1 should have the expected behavior. For example:

$offset = 10;
$limite = 3;

In that case $start = 7, since $start = $offset - $limit = 7 and $end = 10 - 7 + $limit + 1 = 7. Then the third parameter of the array_slice function will be size 7, going from Dice 7 to Dice 13 (8 letter up to 14 letter of the alphabet).

<?php
$collection = range( 'A', 'Z' );
$offset     = 20;
$limit      = 2;

$length = count( $collection );

if( $offset >= $length ) {
    throw new \OutOfRangeException( 'Requested offset exceeds the size of Collection' );
}

$start = ( $offset - $limit ) >= 0 ? ( $offset - $limit ) : 0;
$end   = $offset - $start + $limit + 1;

$slice = array_slice( $collection, $start, $end );

var_dump($slice);
  • Although an automated test would be better, so I can manually test your solution for the $end killed the problem. However, before marking as solved, reading the manual on array_slice() I saw that if the third parameter length for negative it will continue including from the end of the array. Some chance your logic will turn negative?

Browser other questions tagged

You are not signed in. Login or sign up in order to post.