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.
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.
Antworten:
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++;
}
Ein etwas verwandtes Wussten Sie schon:
Es gibt nützliche Methoden java.util.Collections
zum Mischen ganzer Sammlungen: Collections.shuffle(List<?>)
und Collections.shuffle(List<?> list, Random rnd)
.
List
Schnittstelle erweitern, nicht für die Set
vom OP diskutierte Schnittstelle.
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 Gruppe 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();
}
}
Concurrent
sind wirklich sicher, diejenigen, die damit umwickelt Collections.synchronized()
sind, sind halb sicher. Auch das OP hat nichts über Parallelität gesagt, daher ist dies eine gültige und gute Antwort.
dta
(dies kann über Guaven erreicht werdenIterators.unmodifiableIterator
). Andernfalls werden die Standardimplementierungen von z. B. removeAll und RetainAll in AbstractSet und seinen Eltern, die mit diesem Iterator arbeiten, Ihre Probleme lösen RandomSet
!
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()
jede Schleife auf, aber seitdemindex < set.size()
diese Überprüfung unnötig ist. Ich sah eine Geschwindigkeitssteigerung von 10-20%, aber YMMV. (Außerdem wird dies 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 kann, 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();
}
}
Wenn Sie dies in Java tun möchten, sollten Sie in Betracht ziehen, die Elemente in eine Sammlung mit wahlfreiem Zugriff (z. B. eine ArrayList) zu kopieren. Denn der Zugriff auf das ausgewählte Element ist teuer (O (n) anstelle von O (1)), es sei denn, Ihr Satz ist klein. [ed: Listenkopie ist auch O (n)]
Alternativ können Sie nach einer anderen Set-Implementierung suchen, die Ihren Anforderungen besser entspricht. Das ListOrderedSet aus Commons Collections sieht vielversprechend aus.
In Java 8:
static <E> E getRandomSetElement(Set<E> set) {
return set.stream().skip(new Random().nextInt(set.size())).findFirst().orElse(null);
}
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())]);
}
List asList = new ArrayList(mySet);
Collections.shuffle(asList);
return asList.get(0);
Dies ist identisch mit der akzeptierten Antwort (Khoth), jedoch werden die unnötigen size
und i
Variablen entfernt.
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 immer noch zufällig, da wir uns auf zufällig (beginnend mit einem zufällig ausgewählten Index) verlassen, um sich 0
über jede Iteration hinweg zu dekrementieren .
if (--random < 0) {
, wo random
erreicht -1
.
Clojure-Lösung:
(defn pick-random [set] (let [sq (seq set)] (nth sq (rand-int (count sq)))))
nth
Element ebenfalls durchlaufen müssen , um das Element zu erhalten seq
.
C ++. Dies sollte relativ schnell gehen, da es nicht erforderlich ist, den gesamten Satz zu durchlaufen oder zu sortieren. Dies sollte bei den meisten modernen Compilern sofort 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 Buckets unterteilt wurden, und schnell einen zufällig ausgewählten Bucket 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;
}
}
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, versuchen Sie es mit einer Probenahme im Reservoir. http://en.wikipedia.org/wiki/Reservoir_sampling .
Collections.shuffle () (wie von wenigen vorgeschlagen) verwendet einen solchen Algorithmus.
Da Sie sagten "Lösungen für andere Sprachen sind ebenfalls 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
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];
}
PHP unter der Annahme, dass "set" ein Array ist:
$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.
Icon hat einen Set-Typ und einen Zufallselement-Operator, unäres "?", Also den Ausdruck
? set( [1, 2, 3, 4, 5] )
erzeugt eine Zufallszahl zwischen 1 und 5.
Der zufällige Startwert wird beim Ausführen eines Programms auf 0 initialisiert, um bei jeder Ausführung unterschiedliche Ergebnisse zu erzielen randomize()
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"]);
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();
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 gibt. Hier ist eine:
Random[]
erzeugt einen Pseudozufalls-Float zwischen 0 und 1. Dieser wird mit der Länge der Liste multipliziert und dann wird die Deckenfunktion verwendet, um auf die nächste Ganzzahl aufzurunden. Dieser Index wird dann extrahiert a
.
Da die Funktionalität von Hash-Tabellen in Mathematica häufig mit Regeln ausgeführt wird und Regeln in Listen gespeichert werden, kann Folgendes verwendet werden:
a = {"Badger" -> 5, "Bird" -> 1, "Fox" -> 3, "Frog" -> 2, "Wolf" -> 4};
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()));
}
Zum Spaß habe ich ein RandomHashSet geschrieben, das auf Ablehnungsstichproben basiert. Es ist ein bisschen hackig, 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 Suchzeit wird mit O (1) amortisiert. (Weil Java HashTable dicht ist).
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;
}
}
}
Am einfachsten mit Java 8 ist:
outbound.stream().skip(n % outbound.size()).findFirst().get()
wo n
ist eine zufällige ganze Zahl. Natürlich ist es von geringerer Leistung als das mit demfor(elem: Col)
Mit Guave 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);
}
Sie können das Set auch auf ein Array übertragen. Verwenden Sie das Array. Es wird wahrscheinlich im kleinen Maßstab funktionieren. Ich sehe, dass die for-Schleife in der am häufigsten gewählten Antwort ohnehin O (n) ist
Object[] arr = set.toArray();
int v = (int) arr[rnd.nextInt(arr.length)];
Wenn Sie wirklich nur "irgendein" Objekt aus dem auswählen möchten Set
, ohne die Zufälligkeit zu garantieren, ist es am einfachsten, das erste vom Iterator zurückgegebene zu nehmen.
Set<Integer> s = ...
Iterator<Integer> it = s.iterator();
if(it.hasNext()){
Integer i = it.next();
// i is a "random" object from set
}
Eine generische Lösung, die Khoths Antwort als Ausgangspunkt verwendet.
/**
* @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;
}
Leider kann dies in keinem der Set-Container der Standardbibliothek effizient (besser als O (n)) durchgeführt werden.
Dies ist seltsam, da es sehr einfach ist, Hash-Sets und Binär-Sets eine zufällige Auswahlfunktion hinzuzufügen. 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ärbaum können Sie zufällig zwischen dem linken oder rechten Teilbaum mit maximal O (log2) Schritten wählen. Ich habe eine Demo der 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 __name__ == '__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, sodass 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 Leistungsgewinn dieser effizienteren Auswahl den Aufwand für die Verwendung einer Python-basierten Sammlung wert ist. Ich könnte es natürlich verfeinern und in C übersetzen, aber das ist mir heute zu viel Arbeit :)