Browse Source

Implemented a new lastN() method in Collection

closes #12531
Jose Lorenzo Rodriguez 7 years ago
parent
commit
56b90b2e75

+ 19 - 0
src/Collection/CollectionInterface.php

@@ -620,6 +620,25 @@ interface CollectionInterface extends Iterator, JsonSerializable
     public function last();
 
     /**
+     * Returns the last N elements of a collection
+     *
+     * ### Examples:
+     *
+     * ```
+     * $items = [1, 2, 3, 4, 5];
+     *
+     * $last = (new Collection($items))->last(3);
+     *
+     * // Result will look like this when converted to array
+     * [3, 4, 5];
+     * ```
+     *
+     * @param int $howMany The number of elements at the end of the collection
+     * @return \Cake\Collection\CollectionInterface
+     */
+    public function lastN($howMany);
+
+    /**
      * Returns a new collection as the result of concatenating the list of elements
      * in this collection with the passed list of elements
      *

+ 59 - 0
src/Collection/CollectionTrait.php

@@ -402,6 +402,65 @@ trait CollectionTrait
     /**
      * {@inheritDoc}
      */
+    public function lastN($howMany)
+    {
+        if ($howMany < 1) {
+            throw new \InvalidArgumentException("lastN requires a number greater than 0");
+        }
+
+        $iterator = $this->optimizeUnwrap();
+        if (is_array($iterator)) {
+            return new Collection(array_slice($iterator, $howMany * -1));
+        }
+
+        if ($iterator instanceof Countable) {
+            $count = count($iterator);
+            if ($count === 0) {
+                return null;
+            }
+            $iterator = new LimitIterator($iterator, max(0, $count - $howMany), $howMany);
+
+            return new Collection($iterator);
+        }
+
+        $generator = function ($iterator, $howMany) {
+            $result = [];
+            $bucket = 0;
+            $offset = 0;
+            $toggleOffset = false;
+
+            foreach ($iterator as $k => $item) {
+                if ($bucket === 0) {
+                    $toggleOffset = !$toggleOffset;
+                }
+
+                $result[$bucket] = [$k, $item];
+                $bucket = (++$bucket) % $howMany;
+
+                if ($toggleOffset) {
+                    $offset++;
+                }
+            }
+
+            $offset = $offset % $howMany;
+            $head = array_slice($result, $offset);
+            $tail = array_slice($result, 0, $offset);
+
+            foreach ($head as $v) {
+                yield $v[0] => $v[1];
+            }
+
+            foreach ($tail as $v) {
+                yield $v[0] => $v[1];
+            }
+        };
+
+        return new Collection($generator($iterator, $howMany));
+    }
+
+    /**
+     * {@inheritDoc}
+     */
     public function append($items)
     {
         $list = new AppendIterator();

+ 72 - 0
tests/TestCase/Collection/CollectionTest.php

@@ -2151,6 +2151,78 @@ class CollectionTest extends TestCase
     }
 
     /**
+     * Tests the lastN() method
+     *
+     * @dataProvider simpleProvider
+     * @return void
+     */
+    public function testLastN($data)
+    {
+        $collection = new Collection($data);
+        $result = $collection->lastN(3)->toArray();
+        $expected = ['b' => 2, 'c' => 3, 'd' => 4];
+        $this->assertEquals($expected, $result);
+    }
+
+    /**
+     * Tests the lastN() method with overflow
+     *
+     * @dataProvider simpleProvider
+     * @return void
+     */
+    public function testLasNtWithOverflow($data)
+    {
+        $collection = new Collection($data);
+        $result = $collection->lastN(10)->toArray();
+        $expected = ['a' => 1, 'b' => 2, 'c' => 3, 'd' => 4];
+        $this->assertEquals($expected, $result);
+    }
+
+    /**
+     * Tests the lastN() with and odd numbers collection
+     *
+     * @dataProvider simpleProvider
+     * @return void
+     */
+    public function testLasNtWithOddData($data)
+    {
+        $collection = new Collection($data);
+        $result = $collection->take(3)->lastN(2)->toArray();
+        $expected = ['b' => 2, 'c' => 3];
+        $this->assertEquals($expected, $result);
+    }
+
+    /**
+     * Tests the lastN() with countable collection
+     *
+     * @return void
+     */
+    public function testLasNtWithCountable()
+    {
+        $collection = new Collection(new ArrayObject([1, 2, 3, 4, 5]));
+        $result = $collection->lastN(2)->toArray();
+        $this->assertEquals([4, 5], $result);
+
+        $result = $collection->lastN(1)->toArray();
+        $this->assertEquals([5], $result);
+    }
+
+    /**
+     * Tests the lastN() with countable collection
+     *
+     * @dataProvider simpleProvider
+     * @return void
+     */
+    public function testLasNtWithNegative($data)
+    {
+        $collection = new Collection($data);
+        $this->expectException(\InvalidArgumentException::class);
+        $result = $collection->lastN(-1)->toArray();
+        $expected = [];
+        $this->assertEquals($expected, $result);
+    }
+
+    /**
      * Tests sumOf with no parameters
      *
      * @return void