Skip to content

Instantly share code, notes, and snippets.

@flowl
Created June 20, 2016 16:17
Show Gist options
  • Save flowl/5208f6c108d2b941a17e44bb3b5b5d72 to your computer and use it in GitHub Desktop.
Save flowl/5208f6c108d2b941a17e44bb3b5b5d72 to your computer and use it in GitHub Desktop.
<?php
class Vehicle
{
protected $brandName;
public function __construct($brandName) { $this->brandName = $brandName; }
public function getBrand() {
return $this->brandName;
}
}
class UniformDistribution
{
protected $items = array();
protected $regenerateCallback;
protected $distinctionCallback;
protected $requiredRatioPerKey;
protected $requiredAmountPerKey;
protected $maxCycles;
protected $maxItems;
public function addItem($item) {
$this->items[] = $item;
return $this;
}
public function setRegenerateCallback(callable $callback) {
$this->regenerateCallback = $callback;
return $this;
}
public function setDistinctionCallback(callable $callback) {
$this->distinctionCallback = $callback;
return $this;
}
public function setRequiredRatioPerKey(array $ratioPerKey) {
$this->requiredRatioPerKey = $ratioPerKey;
return $this;
}
public function setMaxCycles($maxCycles) {
$this->maxCycles = $maxCycles;
return $this;
}
public function setMaxItems($maxItems) {
$this->maxItems = $maxItems;
return $this;
}
protected function getRatio() {
$ratio = array();
$total = count($this->items);
foreach ($this->items as $item) {
$key = call_user_func_array($this->distinctionCallback, array($item));
$ratio[$key]['abs'] = isset($ratio[$key]['abs']) ? $ratio[$key]['abs'] + 1 : 1;
$ratio[$key]['pct'] = $total === 0 ? 0 : ($ratio[$key]['abs'] / $total) * 100;
}
return $ratio;
}
protected function regenerateItems() {
$more = call_user_func($this->regenerateCallback);
$this->items = array_merge($this->items, $more);
return $this;
}
protected function hasRatioPerKey($key) {
return isset($this->requiredRatioPerKey[$key]);
}
protected function removeItemType($key, $amount = 1) {
echo 'Removing ' . $amount . ' ' . $key, PHP_EOL;
$realRemoved = 0;
foreach ($this->items as $index => $item) {
$itemKey = call_user_func_array($this->distinctionCallback, array($item));
if ($itemKey === $key) {
unset($this->items[$index]);
$amount--;
$realRemoved++;
}
if ($amount === 0) {
break;
}
}
return $realRemoved;
}
protected function getRequiredAmount($key) {
return isset($this->requiredAmountPerKey[$key])
? $this->requiredAmountPerKey[$key]
: 0;
}
protected function refreshRequiredAmountsPerKey() {
$ratio = $this->getRatio();
$keys = array();
$fixedPercentage = array_sum($this->requiredRatioPerKey);
$availablePercentage = 100 - $fixedPercentage;
// Zähle alle Typen ohne fixen Anteil
foreach ($ratio as $key => $val) {
if (! isset($this->requiredRatioPerKey[$key])) {
$keys[] = $key;
}
}
if (count($keys) !== 0) {
// Wie viel % bleiben für nicht fixe Typen über?
$percentagePerSoftKey = $availablePercentage / count($keys);
// Prozent in Absolute zahlen umwandeln
// ... für die variablen / soften Typen
foreach ($keys as $key) {
$this->requiredAmountPerKey[$key] = ($this->maxItems / 100) * $percentagePerSoftKey;
}
}
// ...für fixe Typen
foreach ($this->requiredRatioPerKey as $key => $value) {
$this->requiredAmountPerKey[$key] = ($this->maxItems / 100) * $value;
}
return $this;
}
protected function reduceItems() {
$ratio = $this->getRatio();
// Wie viele Items müssen gelöscht werden um Platz für andere zu machen
$shift = 0;
foreach ($ratio as $key => $item) {
$required = $this->requiredAmountPerKey[$key];
echo 'required ' . $required, PHP_EOL;
// Typ mit zu wenigen Items
if ($item['abs'] < $required) {
$shift += ($required - $item['abs']);
}
echo 'shift ' . $shift, PHP_EOL;
// Typ mit genug Items (nur wenn andere zu wenig haben)
if ($item['abs'] > $required) {
// Lösche die Hälfte der Items die "zu viel" sind
$amount = floor(($item['abs'] - $required) / 2);
$realRemoved = $this->removeItemType($key, $amount);
$shift -= $realRemoved;
}
echo 'shift ' . $shift, PHP_EOL;
}
echo json_encode($this->getRatio()), PHP_EOL;
}
public function run() {
$this->regenerateItems();
$this->refreshRequiredAmountsPerKey();
$loop = true;
$cycle = 0;
while($loop) {
$cycle++;
$this->regenerateItems();
$this->refreshRequiredAmountsPerKey();
$this->reduceItems();
echo 'Cycle: ' . $cycle, PHP_EOL;
$cyclesOk = $cycle < $this->maxCycles;
$amountOk = count($this->items) >= $this->maxItems;
$ratioOk = $this->
$loop =
$cyclesOk
&& ! $amountOk
&& ! $ratioOk;
}
echo json_encode($this->getRatio()), PHP_EOL;
return $this->items;
}
}
$alphabet = array('A', 'B', 'C', 'D', 'E', 'F','G');
$ud = new UniformDistributionHandler();
foreach ($alphabet as $letter) {
$ud->addItem(new Vehicle($letter));
}
// Muss den "GROUP BY" Wert zurückliefern
$ud->setDistinctionCallback(function(Vehicle $item) { return $item->getBrand(); });
// Muss neue Items zurückliefern
$ud->setRegenerateCallback(function() use ($alphabet) {
$letters = array();
for ($i = 0; $i < 1000; $i++) {
$letters[] = new Vehicle($alphabet[mt_rand(0, count($alphabet) - 1)]);
}
return $letters;
});
// Items mit bestimmter Mindestmenge (in %)
$ud->setRequiredRatioPerKey(array('A' => 30, 'B' => 15));
$ud->setMaxCycles(50);
$ud->setMaxItems(500);
$items = $ud->run();
echo 'final total: ' . count($items), PHP_EOL, PHP_EOL;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment