*/ private array $allocations = []; /** * @param Collection $buckets */ public function __construct( private readonly Collection $buckets, private readonly int $income, ) { } public function execute(): Result { $toAllocate = $this->income; if ($toAllocate <= 0 || $this->buckets->isEmpty()) { return new Result([], 0, 0); } $needs = $this->bucketsOfType(BucketType::NEED); $wants = $this->bucketsOfType(BucketType::WANT); $overflow = $this->bucketsOfType(BucketType::OVERFLOW)[0] ?? null; $phases = [ [$needs, FillStage::Base], [$wants, FillStage::Base], [$needs, FillStage::Buffer], [$wants, FillStage::Buffer], ]; if (!empty($overflow)) { $phases[] = [[$overflow], FillStage::Buffer]; } $remaining = $toAllocate; foreach ($phases as [$group, $fillStage]) { $remaining = $this->allocateToGroup($group, $fillStage, $remaining); } $totalAllocated = array_sum($this->allocations); return new Result($this->getAllocations(), $totalAllocated, $toAllocate - $totalAllocated); } /** * @return list */ private function getAllocations(): array { return $this->buckets->map(fn (Bucket $bucket) => [ 'bucket' => $bucket, 'amount' => $this->allocations[(string) $bucket->getId()] ?? 0, ])->toArray(); } /** * @return list */ private function bucketsOfType(BucketType $type): array { return $this->buckets ->filter(static fn (Bucket $bucket) => $type === $bucket->getType()) ->getValues(); } /** * @param list $group */ private function allocateToGroup(array $group, FillStage $fillStage, int $toAllocate): int { $tiers = []; foreach ($group as $bucket) { $tiers[$bucket->getPriority()][] = $bucket; } ksort($tiers); foreach ($tiers as $tier) { $toAllocate = $this->allocateToTier($tier, $fillStage, $toAllocate); } return $toAllocate; } /** * @param list $group */ private function allocateToTier(array $group, FillStage $fillStage, int $toAllocate): int { $allocations = $this->allocations; $groupBuckets = new ArrayCollection($group); $split = (new EvenSplitter())->split( amount: $toAllocate, slots: $groupBuckets->map(fn (Bucket $bucket) => (new BucketRoomCalculator()) ->roomFor($bucket, $fillStage, $this->income, $allocations[(string) $bucket->getId()] ?? 0)) ->toArray() ); for ($i = 0; $i < \count($split); ++$i) { $bucket = $group[$i]; $amount = $split[$i]; $this->addAllocation($bucket, $amount); $toAllocate -= $amount; } return $toAllocate; } private function addAllocation(Bucket $bucket, int $amount): void { if (isset($this->allocations[(string) $bucket->getId()])) { $bucketAllocation = (int) $this->allocations[(string) $bucket->getId()]; $this->allocations[(string) $bucket->getId()] = $bucketAllocation + $amount; return; } $this->allocations[(string) $bucket->getId()] = $amount; } }