Bir kümeden rastgele bir öğeyi nasıl seçerim? Özellikle bir HashSet veya LinkedHashSet, Java rastgele bir öğe seçmek ilgileniyorum. Diğer diller için de çözümler bekliyoruz.
Bir kümeden rastgele bir öğeyi nasıl seçerim? Özellikle bir HashSet veya LinkedHashSet, Java rastgele bir öğe seçmek ilgileniyorum. Diğer diller için de çözümler bekliyoruz.
Yanıtlar:
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++;
}
Biraz ilgili Bildiniz mi:
java.util.Collections
Tüm koleksiyonları karıştırmak için yararlı yöntemler vardır : Collections.shuffle(List<?>)
ve Collections.shuffle(List<?> list, Random rnd)
.
List
değil, Set
arayüzü genişleten koleksiyonlar için geçerlidir .
A ArrayList
ve a HashMap
: [element -> index] kullanarak Java için hızlı çözüm .
Motivasyon: RandomAccess
Özellikle setten rastgele bir öğe seçmek için özelliklere sahip bir dizi öğeye ihtiyacım vardı ( pollRandom
yönteme bakınız ). İkili ağaçta rastgele gezinme doğru değildir: ağaçlar düzgün bir şekilde dengelenmez, bu da düzgün bir dağılıma yol açmaz.
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
olanlar gerçekten güvenlidir, sarılmış olanlar Collections.synchronized()
yarı güvenlidir. Ayrıca OP eşzamanlılık hakkında hiçbir şey söylemedi, bu yüzden bu geçerli ve iyi bir cevap.
dta
(bu, Iterators.unmodifiableIterator
örneğin guava'lar aracılığıyla elde edilebilir ). Aksi takdirde, AbstractSet ve bu yineleyiciyle çalışan ebeveynleri gibi removeAll ve retainAll öğelerinin varsayılan uygulamaları sizin durumunuzu bozacaktır RandomSet
!
Bu, kabul edilen yanıttaki her bir for döngüsünden daha hızlıdır:
int index = rand.nextInt(set.size());
Iterator<Object> iter = set.iterator();
for (int i = 0; i < index; i++) {
iter.next();
}
return iter.next();
For-each yapısı Iterator.hasNext()
her döngüyü çağırır , ancak o zamandan beri index < set.size()
bu kontrol gereksiz bir ek yüktür. Hızda% 10-20'lik bir artış gördüm, ancak YMMV. (Ayrıca, bu ek bir iade ifadesi eklemek zorunda kalmadan derler.)
Bu kodun (ve diğer cevapların çoğunun) yalnızca Set'e değil, herhangi bir Koleksiyona da uygulanabileceğini unutmayın. Genel yöntem formunda:
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();
}
}
Java ile yapmak istiyorsanız, öğeleri bir çeşit rastgele erişim koleksiyonuna (ArrayList gibi) kopyalamayı düşünmelisiniz. Çünkü, kümeniz küçük olmadığı sürece, seçilen öğeye erişmek pahalı olacaktır (O (1) yerine O (n)). [ed: liste kopyası da O (n)]
Alternatif olarak, gereksinimlerinize daha uygun başka bir Set uygulaması arayabilirsiniz. ListOrderedSet Commons Koleksiyonları'ndan umut verici görünüyor.
Java 8'de:
static <E> E getRandomSetElement(Set<E> set) {
return set.stream().skip(new Random().nextInt(set.size())).findFirst().orElse(null);
}
Java dilinde:
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);
Bu, kabul edilen cevap (Khoth) ile aynıdır, ancak gereksiz size
ve i
değişkenler kaldırılmıştır.
int random = new Random().nextInt(myhashSet.size());
for(Object obj : myhashSet) {
if (random-- == 0) {
return obj;
}
}
Yukarıda bahsedilen iki değişkeni ortadan kaldırsa da, yukarıdaki çözüm hala rasgele kalmaktadır, çünkü 0
her yinelemenin üzerine doğru kendini azaltmak için rastgele (rastgele seçilen bir dizinden başlayarak) güveniyoruz .
if (--random < 0) {
, nerede random
ulaşır -1
.
Clojure çözümü:
(defn pick-random [set] (let [sq (seq set)] (nth sq (rand-int (count sq)))))
nth
öğeyi elde etmek için de geçiş yapmanız gerekir seq
.
C ++. Bu, tüm set üzerinde tekrarlama veya sıralama gerektirmediği için makul derecede hızlı olmalıdır. Bu, tr1'i destekledikleri varsayılarak, çoğu modern derleyiciyle birlikte çalışmalıdır . Değilse, Boost'u kullanmanız gerekebilir.
Boost dokümanlar Kuvvetlendirme kullanmayın bile, bu açıklamaya burada faydalıdır.
İşin püf noktası, verilerin bölümlere ayrıldığı gerçeğinden faydalanmak ve rasgele seçilen bir kovayı (uygun olasılıkla) hızlı bir şekilde tanımlamaktır.
//#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;
}
}
Yukarıdaki çözüm gecikme anlamındadır, ancak seçilen her bir endeksin eşit olasılığını garanti etmez.
Dikkat edilmesi gerekiyorsa, rezervuar örneklemeyi deneyin. http://en.wikipedia.org/wiki/Reservoir_sampling .
Collections.shuffle () (birkaç kişi tarafından önerildiği gibi) böyle bir algoritma kullanır.
"Diğer diller için çözümler de hoş geldiniz" dediğinden, Python için sürüm:
>>> import random
>>> random.choice([1,2,3,4,5,6])
3
>>> random.choice([1,2,3,4,5,6])
4
Sadece set / dizinin boyutunu / uzunluğunu elde edemez, 0 ile boyut / uzunluk arasında rastgele bir sayı üretemez, ardından dizini bu sayı ile eşleşen öğeyi çağıramaz mısınız? HashSet .size () yöntemine sahiptir, eminim.
Psuedocode'ta -
function randFromSet(target){
var targetLength:uint = target.length()
var randomIndex:uint = random(0,targetLength);
return target[randomIndex];
}
PHP, "set" in bir dizi olduğunu varsayar:
$foo = array("alpha", "bravo", "charlie");
$index = array_rand($foo);
$val = $foo[$index];
Mersenne Twister fonksiyonları daha iyidir ancak PHP'de array_rand'ın MT eşdeğeri yoktur.
Simgenin bir set türü ve rastgele eleman operatörü var, tekli "?"
? set( [1, 2, 3, 4, 5] )
1 ile 5 arasında rastgele bir sayı üretecektir.
Bir program çalıştırıldığında rastgele tohum 0 olarak başlatılır, böylece her çalıştırma kullanımında farklı sonuçlar elde edilir randomize()
C # dilinde
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 çözümü;)
function choose (set) {
return set[Math.floor(Math.random() * set.length)];
}
var set = [1, 2, 3, 4], rand = choose (set);
Veya alternatif olarak:
Array.prototype.choose = function () {
return this[Math.floor(Math.random() * this.length)];
};
[1, 2, 3, 4].choose();
Mathematica'da:
a = {1, 2, 3, 4, 5}
a[[ ⌈ Length[a] Random[] ⌉ ]]
Veya son sürümlerde basitçe:
RandomChoice[a]
Bu, belki de açıklama eksik olduğu için bir aşağı oy aldı, bu yüzden burada:
Random[]
0 ile 1 arasında bir sahte sözde float oluşturur. Bu, listenin uzunluğuyla çarpılır ve ardından tavan işlevi, bir sonraki tamsayıya yuvarlamak için kullanılır. Bu indeks daha sonra buradan çıkarılır a
.
Karma tablo işlevi Mathematica'daki kurallarla sık sık yapıldığından ve kurallar listelerde saklandığından, aşağıdakiler kullanılabilir:
a = {"Badger" -> 5, "Bird" -> 1, "Fox" -> 3, "Frog" -> 2, "Wolf" -> 4};
Peki ya
public static <A> A getRandomElement(Collection<A> c, Random r) {
return new ArrayList<A>(c).get(r.nextInt(c.size()));
}
Eğlence için ret örneklemesine dayalı bir RandomHashSet yazdım. HashMap tabloya doğrudan erişmemize izin vermediğinden, biraz hileli, ama iyi çalışmalı.
Fazladan bellek kullanmaz ve arama süresi O (1) itfa edilir. (Java HashTable yoğun olduğundan).
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;
}
}
}
Java 8 ile en kolayı:
outbound.stream().skip(n % outbound.size()).findFirst().get()
burada n
rastgele bir tam sayıdır. Tabii ki daha az performansa sahip.for(elem: Col)
Guava ile Khoth'un cevabından biraz daha iyisini yapabiliriz:
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);
}
Eğer Set
rastgele herhangi bir garanti olmadan, sadece "herhangi" bir nesne seçmek istiyorsanız , en kolay ilk yineleyici tarafından iade edilir.
Set<Integer> s = ...
Iterator<Integer> it = s.iterator();
if(it.hasNext()){
Integer i = it.next();
// i is a "random" object from set
}
Khoth'un cevabını başlangıç noktası olarak kullanan genel bir çözüm.
/**
* @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;
}
Ne yazık ki, bu Standart Kütüphane seti kapsayıcılarında verimli bir şekilde (O (n) 'den daha iyi) yapılamaz.
Karma kümelere ve ikili kümelere rastgele seçim fonksiyonu eklemek çok kolay olduğu için bu garip. Seyrek bir karma kümesinde, bir hit elde edene kadar rastgele girişleri deneyebilirsiniz. İkili ağaç için, maksimum O (log2) adımla, sol veya sağ alt ağaç arasında rastgele seçim yapabilirsiniz. Aşağıdakilerin bir demosunu uyguladım:
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
Çıktı olarak [995, 975, 971, 995, 1057, 1004, 966, 1052, 984, 1001] çıktı.
Kendim için de aynı sorunla mücadele ettim ve hava durumuna henüz bu daha verimli seçimin performans kazancının bir python tabanlı koleksiyon kullanmanın yükü olduğuna karar vermedim. Elbette rafine edebilir ve C'ye çevirebilirdim, ama bugün benim için çok fazla iş var :)