it-swarm.com.de

Ein zufälliges Element aus einer Menge auswählen

Wie wähle ich ein zufälliges Element aus einer Menge aus? Ich bin besonders daran interessiert, ein zufälliges Element aus einem HashSet oder einem LinkedHashSet in Java auszuwählen. Lösungen für andere Sprachen sind ebenfalls willkommen.

169
Clue Less
int size = myHashSet.size();
int item = new Random().nextInt(size); // In real life, the Random object should be rather more shared than this
int i = 0;
for(Object obj : myhashSet)
{
    if (i == item)
        return obj;
    i++;
}
81
Khoth

Ein etwas verwandter Wussten Sie, dass:

Es gibt nützliche Methoden in Java.util.Collections zum Mischen ganzer Sammlungen: Collections.shuffle(List<?>) und Collections.shuffle(List<?> list, Random rnd) .

72

Schnelle Lösung für Java mit einem ArrayList und einem HashMap: [element -> index].

Motivation: Ich brauchte eine Reihe von Elementen mit RandomAccess -Eigenschaften, insbesondere um ein zufälliges Element aus der Reihe auszuwählen (siehe pollRandom -Methode). Die zufällige Navigation in einem Binärbaum ist nicht genau: Bäume sind nicht perfekt ausbalanciert, was nicht zu einer gleichmäßigen Verteilung führen würde.

public class RandomSet<E> extends AbstractSet<E> {

    List<E> dta = new ArrayList<E>();
    Map<E, Integer> idx = new HashMap<E, Integer>();

    public RandomSet() {
    }

    public RandomSet(Collection<E> items) {
        for (E item : items) {
            idx.put(item, dta.size());
            dta.add(item);
        }
    }

    @Override
    public boolean add(E item) {
        if (idx.containsKey(item)) {
            return false;
        }
        idx.put(item, dta.size());
        dta.add(item);
        return true;
    }

    /**
     * Override element at position <code>id</code> with last element.
     * @param id
     */
    public E removeAt(int id) {
        if (id >= dta.size()) {
            return null;
        }
        E res = dta.get(id);
        idx.remove(res);
        E last = dta.remove(dta.size() - 1);
        // skip filling the hole if last is removed
        if (id < dta.size()) {
            idx.put(last, id);
            dta.set(id, last);
        }
        return res;
    }

    @Override
    public boolean remove(Object item) {
        @SuppressWarnings(value = "element-type-mismatch")
        Integer id = idx.get(item);
        if (id == null) {
            return false;
        }
        removeAt(id);
        return true;
    }

    public E get(int i) {
        return dta.get(i);
    }

    public E pollRandom(Random rnd) {
        if (dta.isEmpty()) {
            return null;
        }
        int id = rnd.nextInt(dta.size());
        return removeAt(id);
    }

    @Override
    public int size() {
        return dta.size();
    }

    @Override
    public Iterator<E> iterator() {
        return dta.iterator();
    }
}
32
fandrew

Dies ist schneller als die For-Each-Schleife in der akzeptierten Antwort:

int index = Rand.nextInt(set.size());
Iterator<Object> iter = set.iterator();
for (int i = 0; i < index; i++) {
    iter.next();
}
return iter.next();

Das for-each-Konstrukt ruft Iterator.hasNext() in jeder Schleife auf, aber seit index < set.size() ist diese Prüfung nicht mehr erforderlich. Ich habe einen Geschwindigkeitszuwachs von 10-20% gesehen, aber YMMV. (Dies wird auch kompiliert, ohne dass eine zusätzliche return-Anweisung hinzugefügt werden muss.)

Beachten Sie, dass dieser Code (und die meisten anderen Antworten) auf jede Sammlung angewendet werden können, nicht nur auf Set. In generischer Methodenform:

public static <E> E choice(Collection<? extends E> coll, Random Rand) {
    if (coll.size() == 0) {
        return null; // or throw IAE, if you prefer
    }

    int index = Rand.nextInt(coll.size());
    if (coll instanceof List) { // optimization
        return ((List<? extends E>) coll).get(index);
    } else {
        Iterator<? extends E> iter = coll.iterator();
        for (int i = 0; i < index; i++) {
            iter.next();
        }
        return iter.next();
    }
}
27
Sean Van Gorder

Wenn Sie dies in Java tun möchten, sollten Sie erwägen, die Elemente in eine Auflistung mit wahlfreiem Zugriff (z. B. eine ArrayList) zu kopieren. Denn solange Ihre Menge nicht klein ist, ist der Zugriff auf das ausgewählte Element teuer (O (n) anstelle von O (1)). [ed: Listenkopie ist auch O (n)]

Alternativ können Sie nach einer anderen Set-Implementierung suchen, die Ihren Anforderungen besser entspricht. Das ListOrderedSet von Commons Collections sieht vielversprechend aus.

15
Dan Dyer

In Java:

Set<Integer> set = new LinkedHashSet<Integer>(3);
set.add(1);
set.add(2);
set.add(3);

Random Rand = new Random(System.currentTimeMillis());
int[] setArray = (int[]) set.toArray();
for (int i = 0; i < 10; ++i) {
    System.out.println(setArray[Rand.nextInt(set.size())]);
}
8
Jorge Ferreira
List asList = new ArrayList(mySet);
Collections.shuffle(asList);
return asList.get(0);
8
Ben Noland

Clojure-Lösung:

(defn pick-random [set] (let [sq (seq set)] (nth sq (Rand-int (count sq)))))
3
pjb3

Perl 5

@hash_keys = (keys %hash);
$Rand = int(Rand(@hash_keys));
print $hash{$hash_keys[$Rand]};

Hier ist eine Möglichkeit, dies zu tun.

2
J.J.

Die obige Lösung bezieht sich auf die Latenz, garantiert jedoch nicht die gleiche Wahrscheinlichkeit, dass jeder Index ausgewählt wird.
Wenn dies berücksichtigt werden muss, probieren Sie die Probenahme aus dem Reservoir. http://en.wikipedia.org/wiki/Reservoir_sampling .
Collections.shuffle () (wie von wenigen vorgeschlagen) verwendet einen solchen Algorithmus.

2
thepace

C++. Dies sollte relativ schnell gehen, da es nicht erforderlich ist, den gesamten Satz zu iterieren oder zu sortieren. Dies sollte bei den meisten modernen Compilern funktionieren, vorausgesetzt, sie unterstützen tr1 . Wenn nicht, müssen Sie möglicherweise Boost verwenden.

Die Boost-Dokumente sind hier hilfreich, um dies zu erklären, auch wenn Sie Boost nicht verwenden.

Der Trick besteht darin, die Tatsache zu nutzen, dass die Daten in Gruppen unterteilt wurden, und eine zufällig ausgewählte Gruppe schnell zu identifizieren (mit der entsprechenden Wahrscheinlichkeit).

//#include <boost/unordered_set.hpp>  
//using namespace boost;
#include <tr1/unordered_set>
using namespace std::tr1;
#include <iostream>
#include <stdlib.h>
#include <assert.h>
using namespace std;

int main() {
  unordered_set<int> u;
  u.max_load_factor(40);
  for (int i=0; i<40; i++) {
    u.insert(i);
    cout << ' ' << i;
  }
  cout << endl;
  cout << "Number of buckets: " << u.bucket_count() << endl;

  for(size_t b=0; b<u.bucket_count(); b++)
    cout << "Bucket " << b << " has " << u.bucket_size(b) << " elements. " << endl;

  for(size_t i=0; i<20; i++) {
    size_t x = Rand() % u.size();
    cout << "we'll quickly get the " << x << "th item in the unordered set. ";
    size_t b;
    for(b=0; b<u.bucket_count(); b++) {
      if(x < u.bucket_size(b)) {
        break;
      } else
        x -= u.bucket_size(b);
    }
    cout << "it'll be in the " << b << "th bucket at offset " << x << ". ";
    unordered_set<int>::const_local_iterator l = u.begin(b);
    while(x>0) {
      l++;
      assert(l!=u.end(b));
      x--;
    }
    cout << "random item is " << *l << ". ";
    cout << endl;
  }
}
2
Aaron McDaid

In Java 8:

static <E> E getRandomSetElement(Set<E> set) {
    return set.stream().skip(new Random().nextInt(set.size())).findFirst().orElse(null);
}
2
Joshua Bone

Dies ist identisch mit der akzeptierten Antwort (Khoth), jedoch ohne die unnötigen Variablen size und i.

    int random = new Random().nextInt(myhashSet.size());
    for(Object obj : myhashSet) {
        if (random-- == 0) {
            return obj;
        }
    }

Obwohl die beiden oben genannten Variablen wegfallen, bleibt die obige Lösung dennoch zufällig, da wir uns darauf verlassen, dass der Zufall (beginnend bei einem zufällig ausgewählten Index) sich bei jeder Iteration in Richtung 0 Dekrementiert.

2
Jason Hartley

Wie wäre es einfach

public static <A> A getRandomElement(Collection<A> c, Random r) {
  return new ArrayList<A>(c).get(r.nextInt(c.size()));
}
1
Daniel Lubarov

Icon hat einen Settyp und einen Zufallselementoperator, unäres "?", Also den Ausdruck

? set( [1, 2, 3, 4, 5] )

erzeugt eine Zufallszahl zwischen 1 und 5.

Der Zufallsstartwert wird beim Ausführen eines Programms auf 0 initialisiert. Verwenden Sie also randomize(), um bei jedem Lauf unterschiedliche Ergebnisse zu erzielen.

1
Hugh Allen

Da Sie sagten "Lösungen für andere Sprachen sind auch willkommen", ist hier die Version für Python:

>>> import random
>>> random.choice([1,2,3,4,5,6])
3
>>> random.choice([1,2,3,4,5,6])
4
1
Swaroop C H

Können Sie nicht einfach die Größe/Länge der Menge/des Arrays ermitteln, eine Zufallszahl zwischen 0 und der Größe/Länge generieren und dann das Element aufrufen, dessen Index mit dieser Zahl übereinstimmt? HashSet hat eine .size () Methode, da bin ich mir ziemlich sicher.

Im Pseudocode -

function randFromSet(target){
 var targetLength:uint = target.length()
 var randomIndex:uint = random(0,targetLength);
 return target[randomIndex];
}
1
matt lohkamp

In Mathematica:

a = {1, 2, 3, 4, 5}

a[[ ⌈ Length[a] Random[] ⌉ ]]

Oder in neueren Versionen einfach:

RandomChoice[a]

Dies wurde abgelehnt, vielleicht weil es keine Erklärung dafür gibt. Hier ist eines:

Random[] erzeugt einen pseudozufälligen Gleitkommawert zwischen 0 und 1. Dieser wird mit der Länge der Liste multipliziert und anschließend mit der Deckenfunktion auf die nächste Ganzzahl aufgerundet. Dieser Index wird dann aus a extrahiert.

Da Hashtabellen häufig mit Regeln in Mathematica ausgeführt werden und Regeln in Listen gespeichert werden, kann Folgendes verwendet werden:

a = {"Badger" -> 5, "Bird" -> 1, "Fox" -> 3, "Frog" -> 2, "Wolf" -> 4};
1
Mr.Wizard

PHP, vorausgesetzt "set" ist ein Array:

$foo = array("alpha", "bravo", "charlie");
$index = array_Rand($foo);
$val = $foo[$index];

Die Mersenne Twister-Funktionen sind besser, aber es gibt kein MT Äquivalent zu array_Rand in PHP.

1
dirtside

Javascript Lösung;)

function choose (set) {
    return set[Math.floor(Math.random() * set.length)];
}

var set  = [1, 2, 3, 4], Rand = choose (set);

Oder alternativ:

Array.prototype.choose = function () {
    return this[Math.floor(Math.random() * this.length)];
};

[1, 2, 3, 4].choose();
1
Mathew Byrne

In C #

        Random random = new Random((int)DateTime.Now.Ticks);

        OrderedDictionary od = new OrderedDictionary();

        od.Add("abc", 1);
        od.Add("def", 2);
        od.Add("ghi", 3);
        od.Add("jkl", 4);


        int randomIndex = random.Next(od.Count);

        Console.WriteLine(od[randomIndex]);

        // Can access via index or key value:
        Console.WriteLine(od[1]);
        Console.WriteLine(od["def"]);
1
Mitch Wheat

Leider kann dies nicht effizient durchgeführt werden (besser als O(n)) in einem der Standard Library Set-Container).

Dies ist seltsam, da es sehr einfach ist, Hash-Mengen und Binärmengen mit einer zufälligen Auswahlfunktion zu versehen. In einem nicht zu spärlichen Hash-Set können Sie zufällige Einträge versuchen, bis Sie einen Treffer erhalten. Für einen binären Baum können Sie nach dem Zufallsprinzip zwischen dem linken und dem rechten Teilbaum wählen, mit maximal O(log2) Schritten. Ich habe eine Demo des folgenden implementiert:

import random

class Node:
    def __init__(self, object):
        self.object = object
        self.value = hash(object)
        self.size = 1
        self.a = self.b = None

class RandomSet:
    def __init__(self):
        self.top = None

    def add(self, object):
        """ Add any hashable object to the set.
            Notice: In this simple implementation you shouldn't add two
                    identical items. """
        new = Node(object)
        if not self.top: self.top = new
        else: self._recursiveAdd(self.top, new)
    def _recursiveAdd(self, top, new):
        top.size += 1
        if new.value < top.value:
            if not top.a: top.a = new
            else: self._recursiveAdd(top.a, new)
        else:
            if not top.b: top.b = new
            else: self._recursiveAdd(top.b, new)

    def pickRandom(self):
        """ Pick a random item in O(log2) time.
            Does a maximum of O(log2) calls to random as well. """
        return self._recursivePickRandom(self.top)
    def _recursivePickRandom(self, top):
        r = random.randrange(top.size)
        if r == 0: return top.object
        Elif top.a and r <= top.a.size: return self._recursivePickRandom(top.a)
        return self._recursivePickRandom(top.b)

if __== '__main__':
    s = RandomSet()
    for i in [5,3,7,1,4,6,9,2,8,0]:
        s.add(i)

    dists = [0]*10
    for i in xrange(10000):
        dists[s.pickRandom()] += 1
    print dists

Ich habe [995, 975, 971, 995, 1057, 1004, 966, 1052, 984, 1001] als Ausgabe erhalten, so dass die Verteilungsnähte gut sind.

Ich habe mit dem gleichen Problem für mich selbst zu kämpfen, und ich habe noch nicht entschieden, ob der Leistungszuwachs dieses effizienteren Plektrums den Aufwand für die Verwendung einer auf python) basierenden Sammlung wert ist Natürlich verfeinere es und übersetze es nach C, aber das ist mir heute zu viel Arbeit :)

1
Thomas Ahle

In LISP

(defun pick-random (set)
       (nth (random (length set)) set))
1
inglesp

Eine generische Lösung, bei der Khoths Antwort als Ausgangspunkt dient.

/**
 * @param set a Set in which to look for a random element
 * @param <T> generic type of the Set elements
 * @return a random element in the Set or null if the set is empty
 */
public <T> T randomElement(Set<T> set) {
    int size = set.size();
    int item = random.nextInt(size);
    int i = 0;
    for (T obj : set) {
        if (i == item) {
            return obj;
        }
        i++;
    }
    return null;
}
0
stivlo

PHP mit MT:

$items_array = array("alpha", "bravo", "charlie");
$last_pos = count($items_array) - 1;
$random_pos = mt_Rand(0, $last_pos);
$random_item = $items_array[$random_pos];
0
da5id

Wenn die festgelegte Größe nicht groß ist, können Sie dies mithilfe von Arrays tun.

int random;
HashSet someSet;
<Type>[] randData;
random = new Random(System.currentTimeMillis).nextInt(someSet.size());
randData = someSet.toArray();
<Type> sResult = randData[random];
0
BHARAT ARYA

Zum Spaß habe ich ein RandomHashSet geschrieben, das auf dem Rejection Sampling basiert. Es ist ein bisschen hacky, da wir mit HashMap nicht direkt auf die Tabelle zugreifen können, aber es sollte gut funktionieren.

Es wird kein zusätzlicher Speicher verwendet und die Nachschlagezeit wird O(1) amortisiert. (Weil Java HashTable ist dicht)).

class RandomHashSet<V> extends AbstractSet<V> {
    private Map<Object,V> map = new HashMap<>();
    public boolean add(V v) {
        return map.put(new WrapKey<V>(v),v) == null;
    }
    @Override
    public Iterator<V> iterator() {
        return new Iterator<V>() {
            RandKey key = new RandKey();
            @Override public boolean hasNext() {
                return true;
            }
            @Override public V next() {
                while (true) {
                    key.next();
                    V v = map.get(key);
                    if (v != null)
                        return v;
                }
            }
            @Override public void remove() {
                throw new NotImplementedException();
            }
        };
    }
    @Override
    public int size() {
        return map.size();
    }
    static class WrapKey<V> {
        private V v;
        WrapKey(V v) {
            this.v = v;
        }
        @Override public int hashCode() {
            return v.hashCode();
        }
        @Override public boolean equals(Object o) {
            if (o instanceof RandKey)
                return true;
            return v.equals(o);
        }
    }
    static class RandKey {
        private Random Rand = new Random();
        int key = Rand.nextInt();
        public void next() {
            key = Rand.nextInt();
        }
        @Override public int hashCode() {
            return key;
        }
        @Override public boolean equals(Object o) {
            return true;
        }
    }
}
0
Thomas Ahle

Mit Guava können wir etwas besser als Khoths Antwort:

public static E random(Set<E> set) {
  int index = random.nextInt(set.size();
  if (set instanceof ImmutableSet) {
    // ImmutableSet.asList() is O(1), as is .get() on the returned list
    return set.asList().get(index);
  }
  return Iterables.get(set, index);
}
0
dimo414

Wenn Sie wirklich nur "irgendein" Objekt aus dem Set auswählen möchten, ohne die Zufälligkeit zu garantieren, ist es am einfachsten, das erste vom Iterator zurückgegebene Objekt zu nehmen.

    Set<Integer> s = ...
    Iterator<Integer> it = s.iterator();
    if(it.hasNext()){
        Integer i = it.next();
        // i is a "random" object from set
    }
0
Philipp

Das einfachste mit Java 8 ist:

outbound.stream().skip(n % outbound.size()).findFirst().get()

dabei ist n eine zufällige Ganzzahl. Natürlich ist es von geringerer Leistung als die mit der Funktion for(elem: Col)

0
Nicu Marasoiu