Wie erstelle ich eine gewichtete Sammlung und wähle dann ein zufälliges Element aus?


34

Ich habe eine Beutebox, die ich mit einem zufälligen Gegenstand füllen möchte. Aber ich möchte, dass jeder Gegenstand eine andere Chance hat, ausgewählt zu werden. Beispielsweise:

  • 5% Chance auf 10 Gold
  • 20% Chance auf Schwert
  • 45% Schildchance
  • 20% Rüstungschance
  • 10% Chance auf Trank

Wie kann ich es schaffen, dass ich genau einen der oben genannten Gegenstände auswähle, bei denen diese Prozentsätze die jeweiligen Chancen auf die Beute darstellen?


1
Zu Ihrer Information: Theoretisch ist für jede endliche Verteilung eine O (1) -Zeit pro Stichprobe möglich, auch für eine Verteilung, deren Einträge sich dynamisch ändern. Siehe z . B. cstheory.stackexchange.com/questions/37648/… .
Neal Young

Antworten:


37

Die Lösung für softcodierte Wahrscheinlichkeiten

Die hartcodierte Wahrscheinlichkeitslösung hat den Nachteil, dass Sie die Wahrscheinlichkeiten in Ihrem Code festlegen müssen. Sie können zur Laufzeit nicht ermittelt werden. Es ist auch schwer zu pflegen.

Hier ist eine dynamische Version desselben Algorithmus.

  1. Erstellen Sie eine Reihe von Paaren von tatsächlichen Artikeln und dem Gewicht jedes Artikels
  2. Wenn Sie einen Artikel hinzufügen, muss das Gewicht des Artikels sein eigenes Gewicht plus die Summe der Gewichte aller Artikel sein, die sich bereits im Array befinden. Sie sollten die Summe also separat verfolgen. Vor allem, weil Sie es für den nächsten Schritt benötigen.
  3. Um ein Objekt abzurufen, generieren Sie eine Zufallszahl zwischen 0 und der Summe der Gewichte aller Elemente
  4. Durchlaufen Sie das Array von Anfang bis Ende, bis Sie einen Eintrag mit einer Gewichtung gefunden haben, die größer oder gleich der Zufallszahl ist

Hier ist eine Beispielimplementierung in Java in Form einer Vorlagenklasse, die Sie für jedes Objekt, das Ihr Spiel verwendet, instanziieren können. Anschließend können Sie mit der Methode Objekte hinzufügen .addEntry(object, relativeWeight)und einen der zuvor hinzugefügten Einträge auswählen.get()

import java.util.ArrayList;
import java.util.List;
import java.util.Random;

public class WeightedRandomBag<T extends Object> {

    private class Entry {
        double accumulatedWeight;
        T object;
    }

    private List<Entry> entries = new ArrayList<>();
    private double accumulatedWeight;
    private Random rand = new Random();

    public void addEntry(T object, double weight) {
        accumulatedWeight += weight;
        Entry e = new Entry();
        e.object = object;
        e.accumulatedWeight = accumulatedWeight;
        entries.add(e);
    }

    public T getRandom() {
        double r = rand.nextDouble() * accumulatedWeight;

        for (Entry entry: entries) {
            if (entry.accumulatedWeight >= r) {
                return entry.object;
            }
        }
        return null; //should only happen when there are no entries
    }
}

Verwendung:

WeightedRandomBag<String> itemDrops = new WeightedRandomBag<>();

// Setup - a real game would read this information from a configuration file or database
itemDrops.addEntry("10 Gold",  5.0);
itemDrops.addEntry("Sword",   20.0);
itemDrops.addEntry("Shield",  45.0);
itemDrops.addEntry("Armor",   20.0);
itemDrops.addEntry("Potion",  10.0);

// drawing random entries from it
for (int i = 0; i < 20; i++) {
    System.out.println(itemDrops.getRandom());
}

Hier ist dieselbe Klasse, die in C # für Ihr Unity-, XNA- oder MonoGame-Projekt implementiert ist:

using System;
using System.Collections.Generic;

class WeightedRandomBag<T>  {

    private struct Entry {
        public double accumulatedWeight;
        public T item;
    }

    private List<Entry> entries = new List<Entry>();
    private double accumulatedWeight;
    private Random rand = new Random();

    public void AddEntry(T item, double weight) {
        accumulatedWeight += weight;
        entries.Add(new Entry { item = item, accumulatedWeight = accumulatedWeight });
    }

    public T GetRandom() {
        double r = rand.NextDouble() * accumulatedWeight;

        foreach (Entry entry in entries) {
            if (entry.accumulatedWeight >= r) {
                return entry.item;
            }
        }
        return default(T); //should only happen when there are no entries
    }
}

Und hier ist einer in JavaScript :

var WeightedRandomBag = function() {

    var entries = [];
    var accumulatedWeight = 0.0;

    this.addEntry = function(object, weight) {
        accumulatedWeight += weight;
        entries.push( { object: object, accumulatedWeight: accumulatedWeight });
    }

    this.getRandom = function() {
        var r = Math.random() * accumulatedWeight;
        return entries.find(function(entry) {
            return entry.accumulatedWeight >= r;
        }).object;
    }   
}

Profi:

  • Kann mit jedem Gewichtsverhältnis umgehen. Sie können Elemente mit astronomisch geringer Wahrscheinlichkeit im Set haben, wenn Sie möchten. Die Gewichte müssen auch nicht zu 100 addieren.
  • Sie können die Artikel und Gewichte zur Laufzeit ablesen
  • Die Speichernutzung ist proportional zur Anzahl der Elemente im Array

Contra:

  • Benötigt etwas mehr Programmierung, um richtig zu werden
  • Im schlimmsten Fall müssen Sie möglicherweise das gesamte Array iterieren ( O(n)Laufzeitkomplexität). Wenn Sie also sehr viele Gegenstände haben und sehr oft zeichnen, kann es langsam werden. Eine einfache Optimierung besteht darin, die wahrscheinlichsten Elemente an die erste Stelle zu setzen, sodass der Algorithmus in den meisten Fällen vorzeitig beendet wird. Eine komplexere Optimierung, die Sie durchführen können, besteht darin, die Tatsache auszunutzen, dass das Array sortiert ist, und eine Halbierungssuche durchzuführen. Das braucht nur O(log n)Zeit.
  • Sie müssen die Liste im Arbeitsspeicher erstellen, bevor Sie sie verwenden können (obwohl Sie zur Laufzeit problemlos Elemente hinzufügen können. Das Entfernen von Elementen kann ebenfalls hinzugefügt werden. Dazu müssen jedoch die akkumulierten Gewichte aller Elemente aktualisiert werden, die nach dem entfernten Eintrag kommen hat wieder O(n)Worst-Case-Laufzeit

2
Der C # -Code könnte mit LINQ: return entries.FirstOrDefault (e => e.accumulatedWeight> = r) geschrieben werden. Noch wichtiger ist, dass dieser Algorithmus aufgrund des Gleitkomma-Präzisionsverlusts möglicherweise null zurückgibt, wenn der Zufallswert nur ein kleines bisschen größer als der akkumulierte Wert ist. Vorsichtshalber könnten Sie dem letzten Element einen kleinen Wert (z. B. 1.0) hinzufügen, aber Sie müssten dann explizit in Ihrem Code angeben, dass die Liste endgültig ist.
IMil

1
Eine kleine Variante dazu habe ich persönlich verwendet. Wenn Sie möchten, dass die Gewichtswerte in der Laufzeit nicht auf den Wert "Gewicht plus alle vorherigen" geändert werden, können Sie das Gewicht jedes übergebenen Eintrags von Ihrem Zufallswert subtrahieren und anhalten, wenn Der zufällige Wert ist kleiner als das aktuelle Artikelgewicht (oder wenn das Gewicht abgezogen wird, wird der Wert <0)
Lunin

2
@ BlueRaja-DannyPflughoeft vorzeitige Optimierung ... die Frage war ein Objekt aus einer offenen Beutebox auszuwählen. Wer öffnet 1000 Kisten pro Sekunde?
IMil

4
@IMil: Nein, die Frage ist ein allgemeiner Haken bei der Auswahl zufällig gewichteter Elemente . Insbesondere für Lootboxes ist diese Antwort wahrscheinlich in Ordnung, da es eine kleine Anzahl von Elementen gibt und sich die Wahrscheinlichkeiten nicht ändern (da diese normalerweise auf einem Server ausgeführt werden, sind 1000 / Sek. Für ein beliebtes Spiel nicht unrealistisch) .
BlueRaja - Danny Pflughoeft

4
@opa markiert dann, um als Betrüger zu schließen. Ist es wirklich falsch, eine gute Antwort zu stimmen, nur weil die Frage schon einmal gestellt wurde?
Baldrickk

27

Hinweis: Für genau dieses Problem habe ich eine C # -Bibliothek erstellt

Die anderen Lösungen sind in Ordnung, wenn Sie nur eine kleine Anzahl von Elementen haben und sich Ihre Wahrscheinlichkeiten nie ändern. Bei vielen Elementen oder sich ändernden Wahrscheinlichkeiten (z. B. Entfernen von Elementen nach ihrer Auswahl) möchten Sie jedoch etwas Stärkeres.

Hier sind die beiden häufigsten Lösungen (beide sind in der obigen Bibliothek enthalten)

Walkers Alias-Methode

Eine clevere Lösung, die extrem schnell ( O(1)!) Ist, wenn Ihre Wahrscheinlichkeiten konstant sind. Im Wesentlichen erstellt der Algorithmus eine 2D-Dartscheibe ("Alias-Tabelle") aus Ihren Wahrscheinlichkeiten und wirft einen Pfeil darauf.

Dartscheibe

Es gibt viele Artikel online darüber, wie es funktioniert, wenn Sie mehr erfahren möchten.

Das einzige Problem ist, dass Sie die Alias-Tabelle, die langsam ist, neu generieren müssen, wenn sich Ihre Wahrscheinlichkeiten ändern. Wenn Sie also Artikel entfernen müssen, nachdem sie kommissioniert wurden, ist dies nicht die richtige Lösung für Sie.

Baumbasierte Lösung

Die andere übliche Lösung besteht darin, ein Array zu erstellen, in dem jedes Element die Summe seiner Wahrscheinlichkeit und aller Elemente davor speichert. Dann generiere einfach eine Zufallszahl aus [0,1] und suche binär, wo diese Zahl in der Liste landet.

Diese Lösung ist sehr einfach zu codieren / zu verstehen, aber das Treffen einer Auswahl ist langsamer als Walkers Alias-Methode, und das Ändern der Wahrscheinlichkeiten ist noch nicht abgeschlossen O(n). Wir können es verbessern, indem wir das Array in einen Binärsuchbaum verwandeln, in dem jeder Knoten die Summe der Wahrscheinlichkeiten in allen Elementen in seinem Unterbaum verfolgt. Wenn wir dann die Zahl aus [0,1] generieren, können wir einfach den Baum hinuntergehen, um das Objekt zu finden, das es darstellt.

Dies gibt uns O(log n)die Möglichkeit, einen Artikel auszuwählen und die Wahrscheinlichkeiten zu ändern! Das macht NextWithRemoval()extrem schnell!

Die Ergebnisse

Im Folgenden finden Sie einige schnelle Benchmarks aus der obigen Bibliothek, in denen diese beiden Ansätze verglichen werden

         WeightedRandomizer Benchmarks | Baum | Tabelle
-------------------------------------------------- ---------------------------------
Add () x10000 + NextWithReplacement () x10: | 4 ms | 2 ms
Add () x10000 + NextWithReplacement () x10000: | 7 ms | 4 ms
Add () x10000 + NextWithReplacement () x100000: | 35 ms | 28 ms
(Add () + NextWithReplacement ()) x10000 (verschachtelt) | 8 ms | 5403 ms
Add () x10000 + NextWithRemoval () x10000: | 10 ms | 5948 ms

Wie Sie sehen, ist die Walker-Alias-Methode für den Sonderfall statischer (sich nicht ändernder) Wahrscheinlichkeiten etwa 50-100% schneller. In den dynamischeren Fällen ist der Baum jedoch um mehrere Größenordnungen schneller !


Die baumbasierte Lösung bietet uns auch eine angemessene Laufzeit ( nlog(n)) beim Sortieren von Elementen nach Gewicht.
Nathan Merrill

2
Ich bin skeptisch gegenüber Ihren Ergebnissen, aber dies ist die richtige Antwort. Nicht sicher , warum dies nicht die Top-Antwort ist, wenn man bedenkt dies ist eigentlich der üblicher Weg , um dieses Problem zu umgehen.
Uhr

Welche Datei enthält die baumbasierte Lösung? Zweitens Ihre Benchmark-Tabelle: Ist Walkers Alias ​​die "Tabelle" -Spalte?
Yakk

1
@Yakk: Der Code für die baumbasierte Lösung ist hier . Es basiert auf einer Open-Source-Implementierung eines AA-Baums . Und "Ja" zu Ihrer zweiten Frage.
BlueRaja - Danny Pflughoeft

1
Der Walker-Teil ist eigentlich nur ein Link.
Akkumulation

17

Die Glücksrad-Lösung

Sie können diese Methode verwenden, wenn die Wahrscheinlichkeiten in Ihrem Artikelpool einen ziemlich großen gemeinsamen Nenner haben und Sie sehr oft daraus zeichnen müssen.

Erstellen Sie eine Reihe von Optionen. Setzen Sie jedoch jedes Element mehrmals ein, wobei die Anzahl der Duplikate jedes Elements proportional zur Wahrscheinlichkeit des Auftretens ist. Im obigen Beispiel haben alle Elemente Wahrscheinlichkeiten, die Multiplikatoren von 5% sind, sodass Sie ein Array mit 20 Elementen wie folgt erstellen können:

10 gold
sword
sword
sword
sword
shield
shield
shield
shield
shield
shield
shield
armor
armor
armor
armor
potion
potion

Wählen Sie dann einfach ein zufälliges Element dieser Liste aus, indem Sie eine zufällige Ganzzahl zwischen 0 und der Länge des Arrays - 1 generieren.

Nachteile:

  • Sie müssen das Array erstellen, wenn Sie zum ersten Mal ein Element generieren möchten.
  • Wenn eines Ihrer Elemente eine sehr geringe Wahrscheinlichkeit haben soll, haben Sie ein wirklich großes Array, das viel Speicherplatz beanspruchen kann.

Vorteile:

  • Wenn Sie das Array bereits haben und mehrmals daraus zeichnen möchten, ist es sehr schnell. Nur eine zufällige Ganzzahl und ein Array-Zugriff.

3
Als hybride Lösung, um den zweiten Nachteil zu vermeiden, können Sie den letzten Steckplatz als "anderen" kennzeichnen und auf andere Weise behandeln, z. B. mit Philipps Array-Ansatz. So können Sie diesen letzten Platz mit einem Array füllen, das eine Chance von 99,9% für einen Trank und eine Chance von nur 0,1% für einen Trank bietet Epic Scepter of the Apocalypse. Ein solcher zweistufiger Ansatz nutzt die Vorteile beider Ansätze.
Cort Ammon

1
Ich benutze eine Variation davon in meinem eigenen Projekt. Ich berechne jeden Gegenstand und jedes Gewicht und speichere diese in einem Array, [('gold', 1),('sword',4),...]addiere alle Gewichte und rolle dann eine Zufallszahl von 0 bis zur Summe, iteriere dann das Array und berechne, wo die Zufallszahl landet (d. H a reduce). Funktioniert gut für Arrays, die häufig aktualisiert werden und keine größeren Speicherprobleme aufweisen.

1
@Thebluefish Diese Lösung ist in meiner anderen Antwort "The Soft-coded Probabilities Solution"
Philipp

7

Die Lösung für hartcodierte Wahrscheinlichkeiten

Der einfachste Weg, ein zufälliges Element aus einer gewichteten Sammlung zu finden, besteht darin, eine Kette von if-else-Anweisungen zu durchlaufen, wobei jedes if-else wahrscheinlich zunimmt, da das vorherige nicht trifft.

int rand = random(100); //Random number between 1 and 100 (inclusive)
if(rand <= 5) //5% chance
{
    print("You found 10 gold!");
}
else if(rand <= 25) //20% chance
{
    print("You found a sword!");
}
else if(rand <= 70) //45% chance
{
    print("You found a shield!");
}
else if(rand <= 90) //20% chance
{
    print("You found armor!");
}
else //10% chance
{
    print("You found a potion!");
}

Der Grund, warum die Bedingungen gleich der Chance sind, zuzüglich aller vorherigen Bedingungen, ist, dass die vorherigen Bedingungen bereits die Möglichkeit beseitigt haben, dass es sich um diese Elemente handelt. Für die Bedingung des Schildes entsprechen else if(rand <= 70)70 der 45% igen Chance des Schildes plus der 5% igen Chance des Goldes und der 20% igen Chance des Schwertes.

Vorteile:

  • Einfach zu programmieren, da keine Datenstrukturen erforderlich sind.

Nachteile:

  • Schwer zu pflegen, da Sie Ihre Drop-Raten in Ihrem Code pflegen müssen. Sie können zur Laufzeit nicht ermittelt werden. Wenn Sie also mehr Zukunftssicherheit wünschen, sollten Sie die anderen Antworten überprüfen.

3
Das zu pflegen wäre echt nervig. Wenn Sie zum Beispiel Gold entfernen möchten und Trank seine Stelle einnehmen möchten, müssen Sie die Wahrscheinlichkeiten aller Gegenstände zwischen ihnen anpassen.
Alexander - Reinstate Monica

1
Um das von @Alexander erwähnte Problem zu vermeiden, können Sie stattdessen die aktuelle Rate bei jedem Schritt subtrahieren, anstatt sie zu jeder Bedingung hinzuzufügen.
AlexanderJ93

2

In C # könnten Sie einen Linq-Scan verwenden, um Ihren Akku mit einer Zufallszahl im Bereich von 0 bis 100.0f zu vergleichen und .First () zu erhalten. Also wie eine Codezeile.

Also so etwas wie:

var item = a.Select(x =>
{
    sum += x.prob;
    if (rand < sum)
        return x.item;
    else
        return null;
 }).FirstOrDefault());

sum ist eine null initialisierte ganze Zahl und a eine Liste von prob / item-Strukturen / Tupeln / Instanzen. randist eine zuvor generierte Zufallszahl im Bereich.

Dies summiert einfach die Summe über die Liste der Bereiche, bis sie die zuvor ausgewählte Zufallszahl überschreitet, und gibt entweder das Element oder null zurück, wobei null zurückgegeben würde, wenn der Zufallszahlenbereich (z. B. 100) versehentlich kleiner als der Gesamtgewichtungsbereich ist , und die ausgewählte Zufallszahl liegt außerhalb des gesamten Gewichtungsbereichs.

Sie werden jedoch feststellen, dass die Gewichte in OP einer Normalverteilung (Bell-Kurve) sehr nahe kommen. Ich denke, im Allgemeinen werden Sie keine bestimmten Bereiche wollen, Sie werden eher eine Verteilung wollen, die sich entweder um eine Glockenkurve oder nur auf einer abnehmenden Exponentialkurve (zum Beispiel) verjüngt. In diesem Fall können Sie einfach eine mathematische Formel verwenden, um einen Index für ein Array von Elementen zu generieren, die in der Reihenfolge der bevorzugten Wahrscheinlichkeit sortiert sind. Ein gutes Beispiel ist CDF in normaler Verteilung

Auch ein Beispiel hier .

Ein anderes Beispiel ist, dass Sie einen zufälligen Wert von 90 Grad bis 180 Grad verwenden könnten, um den unteren rechten Quadranten eines Kreises zu erhalten, die x-Komponente mit cos (r) zu nehmen und damit in eine priorisierte Liste zu indexieren.

Mit verschiedenen Formeln könnten Sie einen allgemeinen Ansatz verfolgen, bei dem Sie einfach eine priorisierte Liste beliebiger Länge (z. B. N) eingeben und das Ergebnis der Formel (z. B. cos (x) ist 0 zu 1) durch Multiplikation (z. B. Ncos (x) abbilden ) = 0 bis N), um den Index zu erhalten.


3
Könnten Sie uns diese Codezeile geben, wenn es nur eine Zeile ist? Ich bin nicht so vertraut mit C #, daher weiß ich nicht, was Sie meinen.
HEGX64

@ HEGX64 hinzugefügt, aber die Verwendung von Handy und Editor funktioniert nicht. Kannst du bearbeiten
Sentinel

4
Können Sie diese Antwort ändern, um das Konzept dahinter zu erläutern, anstatt eine bestimmte Implementierung in einer bestimmten Sprache?
Raimund Krämer

@ RaimundKrämer Ähm, fertig?
Sentinel

Ablehnung ohne Erklärung = nutzlos und unsozial.
WGroleau

1

Wahrscheinlichkeiten müssen nicht fest codiert sein. Die Elemente und die Schwellenwerte können zusammen in einem Array sein.

for X in itemsrange loop
  If items (X).threshold < random() then
     Announce (items(X).name)
     Exit loop
  End if
End loop

Sie müssen die Schwellenwerte noch akkumulieren, können dies jedoch beim Erstellen einer Parameterdatei tun, anstatt sie zu codieren.


3
Könnten Sie näher erläutern, wie der richtige Schwellenwert berechnet wird? Wenn Sie beispielsweise drei Elemente mit einer Wahrscheinlichkeit von jeweils 33% haben, wie würden Sie diese Tabelle erstellen? Da jedes Mal ein neues random () generiert wird, würde das erste 0,3333, das zweite 0,5 und das letzte 1,0 benötigen. Oder habe ich den Algorithmus falsch gelesen?
Pipe

Sie berechnen es so, wie es andere in ihren Antworten getan haben. Für gleiche Wahrscheinlichkeiten von X-Elementen ist der erste Schwellenwert 1 / X, der zweite, 2 / X usw.
WGroleau

Wenn Sie dies für 3 Elemente in diesem Algorithmus tun, erhalten Sie die Schwellenwerte 1/3, 2/3 und 3/3, aber die Ergebniswahrscheinlichkeiten 1/3, 4/9 und 2/9 für das erste, zweite und dritte Element. Meinst du wirklich, den Anruf random()in der Schleife zu haben?
Pfeife

Nein, das ist definitiv ein Fehler. Jeder Scheck benötigt die gleiche Zufallszahl.
WGroleau

0

Ich habe diese Funktion ausgeführt: https://github.com/thewheelmaker/GDscript_Weighted_Random Now! in deinem Fall kannst du es so benutzen:

on_normal_case([5,20,45,20,10],0)

Es gibt nur eine Zahl zwischen 0 und 4, aber Sie können sie in ein Array einfügen, in dem Sie die Gegenstände erhalten haben.

item_array[on_normal_case([5,20,45,20,10],0)]

Oder in Funktion:

item_function(on_normal_case([5,20,45,20,10],0))

Hier ist der Code. Ich habe es auf GDscript gemacht, das können Sie, aber es kann eine andere Sprache ändern, auch nach logischen Fehlern suchen:

func on_normal_case(arrayy,transformm):
    var random_num=0
    var sum=0
    var summatut=0
    #func sumarrays_inarray(array):
    for i in range(arrayy.size()):
        sum=sum+arrayy[i]
#func no_fixu_random_num(here_range,start_from):
    random_num=randi()%sum+1
#Randomies be pressed down
#first start from zero
    if 0<=random_num and random_num<=arrayy[0]:
        #print(random_num)
        #print(array[0])
        return 0+ transformm
    summatut=summatut+arrayy[0]
    for i in range(arrayy.size()-1):
        #they must pluss together
        #if array[i]<=random_num and random_num<array[i+1]:
        if summatut<random_num and random_num<=summatut+arrayy[i+1]:
            #return i+1+transform
            #print(random_num)
            #print(summatut)
            return i+1+ transformm

        summatut=summatut+arrayy[i+1]
    pass

Es funktioniert so: on_normal_case ([50,50], 0) Dies ergibt 0 oder 1, es hat beide die gleiche Wahrscheinlichkeit.

on_normal_case ([50,50], 1) Dies ergibt 1 oder 2, beide haben die gleiche Wahrscheinlichkeit.

on_normal_case ([20,80], 1) Dies ergibt 1 oder 2, es gibt eine größere Änderung, um zwei zu erhalten.

on_normal_case ([20,80,20,20,30], 1) Dies ergibt einen Zufallszahlenbereich von 1-5 und größere Zahlen sind wahrscheinlicher als kleinere Zahlen.

on_normal_case ([20,80,0,0,20,20,30,0,0,0,33], 45) Dieser Wurf würfelt zwischen den Zahlen 45,46,49,50,51,56, die Sie sehen, wenn Sie dort sind Null ist, kommt es nie vor.

Die Funktion gibt also nur eine Zufallszahl zurück, die von der Länge des Arrays und der Transformationsnummer abhängt, und die Angaben im Array sind Wahrscheinlichkeitsgewichte, mit denen eine Zahl auftreten kann, bei der sich diese Zahl auf dem Array befindet.

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.