Files
timebank-cc-public/app/Services/Stats/SlidingWindow.php
T

78 lines
2.3 KiB
PHP

<?php
namespace App\Services\Stats;
/**
* Which keys occur inside a date window that only ever moves forward.
*
* Built for the network time-lapse: its layers are windows over a day-ordered
* list of [day, key] events - "traded in the month ending on this step",
* "traded before this date" - and each step moves them on by a week or a
* month. Two pointers walk the events once in total, so all steps together
* cost one pass, and each step reports only the keys that entered or left.
*
* Days are 'Y-m-d' strings, which compare correctly as strings.
*/
class SlidingWindow
{
/** @var array<string,int> Occurrences of each key inside the window. */
private array $count = [];
private int $enter = 0;
private int $leave = 0;
private int $size;
/** @param array<array{0:string, 1:string}> $events [day, key], ordered by day */
public function __construct(private array $events)
{
$this->size = count($events);
}
/**
* Move the window to [$start, $end] (inclusive; null start = no lower
* bound) and return what entered and left since the previous call.
*
* Both bounds may only move forward.
*
* @return array{added: array<string>, removed: array<string>}
*/
public function slideTo(?string $start, string $end): array
{
$before = [];
while ($this->enter < $this->size && $this->events[$this->enter][0] <= $end) {
$key = $this->events[$this->enter++][1];
$before[$key] ??= ($this->count[$key] ?? 0) > 0;
$this->count[$key] = ($this->count[$key] ?? 0) + 1;
}
if ($start !== null) {
while ($this->leave < $this->enter && $this->events[$this->leave][0] < $start) {
$key = $this->events[$this->leave++][1];
$before[$key] ??= ($this->count[$key] ?? 0) > 0;
if (--$this->count[$key] === 0) {
unset($this->count[$key]);
}
}
}
$added = [];
$removed = [];
foreach ($before as $key => $was) {
$now = isset($this->count[$key]);
if ($now && !$was) {
$added[] = $key;
} elseif (!$now && $was) {
$removed[] = $key;
}
}
return ['added' => $added, 'removed' => $removed];
}
}