From 4fa81584fe918253c5fec4cc55e6c03b15da0065 Mon Sep 17 00:00:00 2001 From: Jose Lorenzo Rodriguez Date: Wed, 5 Sep 2018 11:16:18 +0200 Subject: [PATCH] Adding an explanation to the dense implementation of lastN --- src/Collection/CollectionTrait.php | 56 +++++++++++++++++++++++++----- 1 file changed, 47 insertions(+), 9 deletions(-) diff --git a/src/Collection/CollectionTrait.php b/src/Collection/CollectionTrait.php index 76e10189f42..f9552c500ee 100644 --- a/src/Collection/CollectionTrait.php +++ b/src/Collection/CollectionTrait.php @@ -429,19 +429,57 @@ public function lastN($howMany) $result = []; $bucket = 0; $offset = 0; - $toggleOffset = false; - foreach ($iterator as $k => $item) { - if ($bucket === 0) { - $toggleOffset = !$toggleOffset; - } + /** + * Consider the collection of elements [1, 2, 3, 4, 5, 6, 7, 8, 9], in order + * to get the last 4 elements, we can keep a buffer of 4 elements and + * fill it circularly using modulo logic, we use the $bucket variable + * to track the position to fill next in the buffer. This how the buffer + * looks like after 4 iterations: + * + * 0) 1 2 3 4 -- $bucket now goes back to 0, we have filled 4 elementes + * 1) 5 2 3 4 -- 5th iteration + * 2) 5 6 3 4 -- 6th iteration + * 3) 5 6 7 4 -- 7th iteration + * 4) 5 6 7 8 -- 8th iteration + * 5) 9 6 7 8 + * + * We can see that at the end of the iterations, the buffer contains all + * the last for elements, just in the wrong order. How do we keep the + * original order? Well, it turns out that the number of iteration also + * give us a clue on what's going on, Let's add a marker for it now: + * + * 0) 1 2 3 4 + * ^ -- The 0) above now becomes the $offset variable + * 1) 5 2 3 4 + * ^ -- $offset = 1 + * 2) 5 6 3 4 + * ^ -- $offset = 2 + * 3) 5 6 7 4 + * ^ -- $offset = 3 + * 4) 5 6 7 8 + * ^ -- We use module logic for $offset too + * and as you can see each time $offset is 0, then the buffer + * is sorted exactly as we need. + * 5) 9 6 7 8 + * ^ -- $offset = 1 + * + * The $offset variable is a marker for splitting the buffer in two, + * elements to the right for the marker are the head of the final result, + * whereas the elements at the left are the tail. For example consider step 5) + * which has an offset of 1: + * + * - $head = elements to the right = [6, 7, 8] + * - $tail = elements to the left = [9] + * - $result = $head + $tail = [6, 7, 8, 9] + * + * The logic above applies to collections of any size. + */ + foreach ($iterator as $k => $item) { $result[$bucket] = [$k, $item]; $bucket = (++$bucket) % $howMany; - - if ($toggleOffset) { - $offset++; - } + $offset++; } $offset = $offset % $howMany;