Як вибрати випадковий елемент із множини? Мені особливо цікаво вибрати випадковий елемент з HashSet або LinkedHashSet на Java. Рішення для інших мов також вітаються.
Як вибрати випадковий елемент із множини? Мені особливо цікаво вибрати випадковий елемент з HashSet або LinkedHashSet на Java. Рішення для інших мов також вітаються.
Відповіді:
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++;
}
Ви дещо пов’язані Чи знаєте ви:
Існують корисні методи java.util.Collections
для переміщення цілих колекцій: Collections.shuffle(List<?>)
і Collections.shuffle(List<?> list, Random rnd)
.
List
інтерфейс, а не Set
інтерфейс, обговорений ОП.
Швидке рішення для Java, використовуючи a ArrayList
і a HashMap
: [елемент -> індекс].
Мотивація: мені знадобився набір елементів із RandomAccess
властивостями, особливо, щоб вибрати випадковий предмет із набору (див. pollRandom
Метод). Випадкова навігація у бінарному дереві не є точною: дерева не є ідеально збалансованими, що не призвело б до рівномірного розподілу.
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
, справді безпечні, ті, що обгорнуті, Collections.synchronized()
є напівбезпечними. Також ОП нічого не сказала про одночасність, тому це правдива і хороша відповідь.
dta
цього (наприклад, це можна досягти, наприклад, через гуаву Iterators.unmodifiableIterator
). В іншому випадку реалізація за замовчуванням, наприклад, RemoveAll і retainAll в AbstractSet та його батьки, які працюють з цим ітератором, зіпсують вашу справу RandomSet
!
Це швидше, ніж для кожного циклу у прийнятій відповіді:
int index = rand.nextInt(set.size());
Iterator<Object> iter = set.iterator();
for (int i = 0; i < index; i++) {
iter.next();
}
return iter.next();
Конструкція for- Iterator.hasNext()
every запускає кожен цикл, але, оскільки index < set.size()
, ця перевірка зайва накладними. Я побачив 10-20% прискорення швидкості, але YMMV. (Крім того, ця компіляція не потребує додавання додаткової заяви повернення.)
Зауважте, що цей код (і більшість інших відповідей) можна застосувати до будь-якої колекції, а не лише до Set. У загальній формі методу:
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, вам слід розглянути можливість копіювання елементів у якусь колекцію випадкового доступу (наприклад, ArrayList). Тому що, якщо ваш набір невеликий, доступ до обраного елемента буде дорогим (O (n) замість O (1)). [ed: список копій також O (n)]
Крім того, ви можете шукати іншу програму Set, яка б більш відповідала вашим вимогам. ListOrderedSet з Commons Колекції виглядає багатообіцяючим.
На Java 8:
static <E> E getRandomSetElement(Set<E> set) {
return set.stream().skip(new Random().nextInt(set.size())).findFirst().orElse(null);
}
На 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);
Це ідентично прийнятій відповіді (Кхот), але з непотрібними size
та i
змінними видалено.
int random = new Random().nextInt(myhashSet.size());
for(Object obj : myhashSet) {
if (random-- == 0) {
return obj;
}
}
Хоча усуваючи дві вищезгадані змінні, вищевказане рішення все ще залишається випадковим, оскільки ми покладаємось на випадкові (починаючи з випадково вибраного індексу), щоб зменшити себе 0
над кожною ітерацією.
if (--random < 0) {
, куди random
доходить -1
.
Рішення Clojure:
(defn pick-random [set] (let [sq (seq set)] (nth sq (rand-int (count sq)))))
nth
елемент, ви також повинні пройти його seq
.
C ++. Це повинно бути досить швидким, оскільки для нього не потрібно повторювати набір чи сортувати його. Це повинно вийти з коробки з більшістю сучасних компіляторів, якщо припустити, що вони підтримують tr1 . Якщо ні, можливо, вам доведеться використовувати Boost.
Документи Boost корисні , щоб пояснити це, навіть якщо ви не використовуєте Boost.
Трюк полягає в тому, щоб скористатися тим, що дані були розділені на відра, і швидко визначити випадково вибраний відрізок (з відповідною ймовірністю).
//#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;
}
}
Наведене вище рішення говорить про затримку, але не гарантує однакової ймовірності вибору кожного індексу.
Якщо це потрібно врахувати, спробуйте відібрати пробу водойми. http://en.wikipedia.org/wiki/Reservoir_sampling .
Collections.shuffle () (як пропонують мало хто) використовує один такий алгоритм.
Оскільки ви сказали, що "Рішення для інших мов також вітаються", ось версія для Python:
>>> import random
>>> random.choice([1,2,3,4,5,6])
3
>>> random.choice([1,2,3,4,5,6])
4
Ви не можете просто отримати розмір / довжину набору / масиву, генерувати випадкове число між 0 та розміром / довжиною, а потім викликати елемент, індекс якого відповідає цьому числу? У HashSet є метод .size (), я впевнений.
У psuedocode -
function randFromSet(target){
var targetLength:uint = target.length()
var randomIndex:uint = random(0,targetLength);
return target[randomIndex];
}
PHP, якщо "set" - це масив:
$foo = array("alpha", "bravo", "charlie");
$index = array_rand($foo);
$val = $foo[$index];
Функції Mersenne Twister є кращими, але в PHP немає еквівалента MT-масиву array_rand.
В 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;)
function choose (set) {
return set[Math.floor(Math.random() * set.length)];
}
var set = [1, 2, 3, 4], rand = choose (set);
Або в якості альтернативи:
Array.prototype.choose = function () {
return this[Math.floor(Math.random() * this.length)];
};
[1, 2, 3, 4].choose();
Математика:
a = {1, 2, 3, 4, 5}
a[[ ⌈ Length[a] Random[] ⌉ ]]
Або в останніх версіях просто:
RandomChoice[a]
Це отримало перемогу в голосуванні, можливо, тому що йому не вистачає пояснень, тож ось таке:
Random[]
генерує псевдовипадковий поплавок між 0 і 1. Це множиться на довжину списку, а потім функція стелі використовується для округлення до наступного цілого числа. Потім цей індекс витягується з a
.
Оскільки функціональність хеш-таблиць часто виконується з правилами в Mathematica, а правила зберігаються в списках, можна використовувати:
a = {"Badger" -> 5, "Bird" -> 1, "Fox" -> 3, "Frog" -> 2, "Wolf" -> 4};
Як щодо просто
public static <A> A getRandomElement(Collection<A> c, Random r) {
return new ArrayList<A>(c).get(r.nextInt(c.size()));
}
Для задоволення я написав RandomHashSet на основі вибірки відхилень. Це трохи хакі, оскільки HashMap не дає нам доступу до його таблиці безпосередньо, але він повинен працювати просто чудово.
Він не використовує додаткової пам'яті, а час пошуку амортизується O (1). (Тому що java HashTable щільна).
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:
outbound.stream().skip(n % outbound.size()).findFirst().get()
де n
випадкове ціле число. Звичайно, це менше продуктивності, ніж уfor(elem: Col)
З Гуавою ми можемо зробити трохи краще, ніж відповідь Хота:
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);
}
Якщо ви просто хочете вибрати "будь-який" об'єкт із Set
, без жодних гарантій на випадковість, найпростіше - це взяти перший, повернутий ітератором.
Set<Integer> s = ...
Iterator<Integer> it = s.iterator();
if(it.hasNext()){
Integer i = it.next();
// i is a "random" object from set
}
Загальне рішення, що використовує відповідь Хота як вихідний пункт.
/**
* @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;
}
На жаль, це неможливо зробити ефективно (краще, ніж O (n)) в будь-якому контейнері набору стандартних бібліотек.
Це не дивно, оскільки додати хеш-набори, а також бінарні набори дуже просто додати функцію рандомізованого вибору. У наборі хешу, який не є рідким, ви можете спробувати випадкові записи, поки не отримаєте хіт. Для двійкового дерева можна вибирати випадковим чином між лівим або правим піддеревом, максимум з кроків O (log2). Я впровадив демонстрацію версії нижче:
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
Я отримав [995, 975, 971, 995, 1057, 1004, 966, 1052, 984, 1001] як вихід, тому шви розподілу хороші.
Я боровся з тією самою проблемою і для себе, і я ще не вирішив, що приріст продуктивності цього більш ефективного вибору вартий витрат на використання колекції на основі пітона. Я, звичайно, міг би вдосконалити та перекласти це на C, але це для мене сьогодні занадто багато роботи :)