Skip to content

Commit

Permalink
Add collection sliding method (#37751)
Browse files Browse the repository at this point in the history
  • Loading branch information
JosephSilber authored Jun 21, 2021
1 parent ad15cf5 commit 4be3354
Show file tree
Hide file tree
Showing 4 changed files with 137 additions and 0 deletions.
16 changes: 16 additions & 0 deletions src/Illuminate/Collections/Collection.php
Original file line number Diff line number Diff line change
Expand Up @@ -959,6 +959,22 @@ public function shuffle($seed = null)
return new static(Arr::shuffle($this->items, $seed));
}

/**
* Create chunks representing a "sliding window" view of the items in the collection.
*
* @param int $size
* @param int $step
* @return static
*/
public function sliding($size = 2, $step = 1)
{
$chunks = floor(($this->count() - $size) / $step) + 1;

return static::times($chunks, function ($number) use ($size, $step) {
return $this->slice(($number - 1) * $step, $size);
});
}

/**
* Skip the first {$count} items.
*
Expand Down
39 changes: 39 additions & 0 deletions src/Illuminate/Collections/LazyCollection.php
Original file line number Diff line number Diff line change
Expand Up @@ -920,6 +920,45 @@ public function shuffle($seed = null)
return $this->passthru('shuffle', func_get_args());
}

/**
* Create chunks representing a "sliding window" view of the items in the collection.
*
* @param int $size
* @param int $step
* @return static
*/
public function sliding($size = 2, $step = 1)
{
return new static(function () use ($size, $step) {
$iterator = $this->getIterator();

$chunk = [];

while ($iterator->valid()) {
$chunk[$iterator->key()] = $iterator->current();

if (count($chunk) == $size) {
yield tap(new static($chunk), function () use (&$chunk, $step) {
$chunk = array_slice($chunk, $step, null, true);
});

// If the $step between chunks is bigger than each chunk's $size,
// we will skip the extra items (which should never be in any
// chunk) before we continue to the next chunk in the loop.
if ($step > $size) {
$skip = $step - $size;

for ($i = 0; $i < $skip && $iterator->valid(); $i++) {
$iterator->next();
}
}
}

$iterator->next();
}
});
}

/**
* Skip the first {$count} items.
*
Expand Down
59 changes: 59 additions & 0 deletions tests/Support/SupportCollectionTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -235,6 +235,65 @@ public function testShiftReturnsAndRemovesFirstItemInCollection()
$this->assertNull($data->first());
}

/**
* @dataProvider collectionClassProvider
*/
public function testSliding($collection)
{
// Default parameters: $size = 2, $step = 1
$this->assertSame([], $collection::times(0)->sliding()->toArray());
$this->assertSame([], $collection::times(1)->sliding()->toArray());
$this->assertSame([[1, 2]], $collection::times(2)->sliding()->toArray());
$this->assertSame(
[[1, 2], [2, 3]],
$collection::times(3)->sliding()->map->values()->toArray()
);

// Custom step: $size = 2, $step = 3
$this->assertSame([], $collection::times(1)->sliding(2, 3)->toArray());
$this->assertSame([[1, 2]], $collection::times(2)->sliding(2, 3)->toArray());
$this->assertSame([[1, 2]], $collection::times(3)->sliding(2, 3)->toArray());
$this->assertSame([[1, 2]], $collection::times(4)->sliding(2, 3)->toArray());
$this->assertSame(
[[1, 2], [4, 5]],
$collection::times(5)->sliding(2, 3)->map->values()->toArray()
);

// Custom size: $size = 3, $step = 1
$this->assertSame([], $collection::times(2)->sliding(3)->toArray());
$this->assertSame([[1, 2, 3]], $collection::times(3)->sliding(3)->toArray());
$this->assertSame(
[[1, 2, 3], [2, 3, 4]],
$collection::times(4)->sliding(3)->map->values()->toArray()
);
$this->assertSame(
[[1, 2, 3], [2, 3, 4]],
$collection::times(4)->sliding(3)->map->values()->toArray()
);

// Custom size and custom step: $size = 3, $step = 2
$this->assertSame([], $collection::times(2)->sliding(3, 2)->toArray());
$this->assertSame([[1, 2, 3]], $collection::times(3)->sliding(3, 2)->toArray());
$this->assertSame([[1, 2, 3]], $collection::times(4)->sliding(3, 2)->toArray());
$this->assertSame(
[[1, 2, 3], [3, 4, 5]],
$collection::times(5)->sliding(3, 2)->map->values()->toArray()
);
$this->assertSame(
[[1, 2, 3], [3, 4, 5]],
$collection::times(6)->sliding(3, 2)->map->values()->toArray()
);

// Ensure keys are preserved, and inner chunks are also collections
$chunks = $collection::times(3)->sliding();

$this->assertSame([[0 => 1, 1 => 2], [1 => 2, 2 => 3]], $chunks->toArray());

$this->assertInstanceOf($collection, $chunks);
$this->assertInstanceOf($collection, $chunks->first());
$this->assertInstanceOf($collection, $chunks->skip(1)->first());
}

/**
* @dataProvider collectionClassProvider
*/
Expand Down
23 changes: 23 additions & 0 deletions tests/Support/SupportLazyCollectionIsLazyTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -897,6 +897,29 @@ public function testShuffleIsLazy()
});
}

public function testSlidingIsLazy()
{
$this->assertDoesNotEnumerate(function ($collection) {
$collection->sliding();
});

$this->assertEnumerates(2, function ($collection) {
$collection->sliding()->take(1)->all();
});

$this->assertEnumerates(3, function ($collection) {
$collection->sliding()->take(2)->all();
});

$this->assertEnumerates(13, function ($collection) {
$collection->sliding(3, 5)->take(3)->all();
});

$this->assertEnumeratesOnce(function ($collection) {
$collection->sliding()->all();
});
}

public function testSkipIsLazy()
{
$this->assertDoesNotEnumerate(function ($collection) {
Expand Down

0 comments on commit 4be3354

Please sign in to comment.