TL;DR
How can I slice an array to get a (user-defined) number of entries before and after a given offset (also user-defined) without go out of range. E.g:
$collection = [ 'A', 'B', 'C', 'D', 'E' ];
If defined a limit of 2 and a starting offset of index 3 (D), the routine should return B
, C
, D
, E
: Two before D
and just one after D
, otherwise it would go out of range.
If defined a limit of 3 and a starting offset of index 0 (A), the routine should return A
, B
, C
, D
, three after A
and none before it, otherwise it would also go out of range.
The slice must expand to always bring the number of elements defined by
$limit
before and after the entry found at$offset
. Unless one of the sides goes out of range, of course
I'm trying to figure a simple, perhaps mathematical only way, to get the offset neighbors of an indexed (and currently sequential) array, but with a custom limit.
First thing that came to me was to use array_slice():
$collection = range( 'A', 'Z' );
$offset = 0; //rand( 0, 25 );
$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 );
From what I could test, $start
is working fine. I'm forcing the first offset to avoid a negative one.
Then I first thought I could just increase $limit
by 2 to have the "second offset", and of course it didn't work, but that was my very fault. I don't use that function so often >.<
Then I changed the logic of $end
to:
$end = $offset + $limit + 1;
The offset I've found in the collection, plus the limit and one more to include the very entry stored in $offset
. It worked fine until $offset
was set to 4 (E) or more. After that, for the letter 'F' the slice was going up to G, H up to I and so on o.O
I tried to search for a solution but what I could find so far were involving loops and complex conditions. As far as I can tell I shouldn't need that, after all if it's sequential, I could apply "simple" math over it.
But mine didn't work so I may be wrong...
This is your cleanest approach -- no conditional calculations required.
Code: (Demo)
Output:
I can confirm that
array_fill_keys()
works just as well in place ofarray_fill()
. Here is another demo:Code: (Demo)
Output:
Edit again.
This is probably as light as you can possibly make this function.
It compares the start and end values with max and min to make sure it doesn't overflow/underflow.
The to output it uses a substr().
But you can also loop it or explode it. See link for that code.
https://3v4l.org/sfCKY
Edit I think I found the best solution. Preg_match.
Sorry for not including an explanation.
I build a regex pattern looking like
.{0,6}B.{0,6}
but 6 and B is variable from the inputs. This means:.{0,6}
- match anything between zero and six times.B
- match a "B", literally..{0,6}
- match anything between zero and six times again.In the regex pattern I use chr() to convert a number ($offset) to a capital letter.
In this case offset is 1 so that means the second letter in alphabet (A is 0).
The $limit is used to, well limit the regex search. {0,
<$limit>
} this means match as many as you can between 0 and $limit.https://3v4l.org/BVdiF
No calculations or array functions at all. Just a regex.
Not sure if I got it correct this time.
But calculate the start and end values of array slice depending on if you overflow or underflow on the offset limit.
https://3v4l.org/0l1J6
Edit found some issues.
If start is 0 I needed to add 1, not otherwise.
End limit of max was not working as it should, thanks for pointing that out.
If limit is 0 error message.
I think I got all variations working now.
Explanations here : https://stackoverflow.com/a/45532145/1636522.
By the way, I beat them all : https://3v4l.org/5Gt7B/perf#output :-P
PHP version :
JS version :