PHP: Zufall mit Gewichtung

Manchmal braucht man etwas mehr als nur einen Zufall, manchmal will man, daß gewisse Optionen häufiger ausgewählt werden als andere. Zufall mit Gewichtung sozusagen.

Das macht zum Beispiel Sinn, wenn man die Last auf Mirror-Servern genauer verteilen will.

Für dieses Problem habe ich mir folgenden Code einfallen lassen, der vielleicht für den ein oder anderen interessant sein könnte.

  1. function wrand($values, $weights = array()) {
  2.          // Summe aller Gewichtungen = 100% der Treffer
  3.          $weightsSum = array_sum($weights);
  4.          // Erhöhe die WeightSum für Values, für die keine Gewichtung hinterlegt ist.
  5.          if (count($weights) < count($values)) {
  6.                  $weightsSum += count($values)count($weights);
  7.          }
  8.          $singleWeightPercent = 100/$weightsSum;
  9.          // newValues enthält später die Werte in ihrer jeweiligen Gewichtung
  10.          $newValues = array();
  11.          // Schreibe jedes Value in newValues, und zwar genau so häufig wie es gewichtet ist.
  12.          for ($i = 0;$i < count($values); $i++) {
  13.                  if ($weights[$i]) {
  14.                          $myWeight = $weights[$i];
  15.                  } else {
  16.                          $myWeight = 1;
  17.                  }
  18.                  for ($j=0; $j<$myWeight; $j++) {
  19.                          array_push($newValues, $values[$i]);
  20.                  }
  21.          }
  22.          // Wähle zufällig einen der Werte aus
  23.          $thisIndex = rand(0, count($newValues)-1);
  24.          return $newValues[$thisIndex];
  25. }

wrand erwartet zwei Parameter: Ein Array mit Werten, ein zweites Array mit numerischen Gewichtungen. Sind weniger Gewichtungen als Werte vorhanden, geht die Maschine von einer Gewichtung von “1″ aus.

Was sind Gewichtungen: Einfache Verhältnisse. Gewichte ich drei Optionen mit (1,1,2), dann erhält die dritte Auswahl so viele zufälligen Treffer wie die beiden ersten zusammen. Bei einer Gewichtung von (1,1,4) erhält die dritte Option vier von sechs Treffern, die ersten beiden jeweils eine. Das Spiel lässt sich endlos fortsetzen [(1,5,3,2,4) …].

Aus Performance-Gründen sollte man die Gewichtungen so klein wie möglich halten. Eine Gewichtung von (25,25,50) mag zwar auf den ersten Blick lesbarer erscheinen, ist aber sehr viel langsamer als (1,1,2).