Làm cách nào để chọn một phần tử ngẫu nhiên từ một tập hợp? Tôi đặc biệt quan tâm đến việc chọn một yếu tố ngẫu nhiên từ Hashset hoặc LinkedHashset, trong Java. Giải pháp cho các ngôn ngữ khác cũng được hoan nghênh.
Làm cách nào để chọn một phần tử ngẫu nhiên từ một tập hợp? Tôi đặc biệt quan tâm đến việc chọn một yếu tố ngẫu nhiên từ Hashset hoặc LinkedHashset, trong Java. Giải pháp cho các ngôn ngữ khác cũng được hoan nghênh.
Câu trả lời:
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++;
}
Một chút liên quan Bạn có biết:
Có các phương pháp hữu ích java.util.Collections
để xáo trộn toàn bộ bộ sưu tập: Collections.shuffle(List<?>)
và Collections.shuffle(List<?> list, Random rnd)
.
List
giao diện, không phải Set
giao diện được thảo luận bởi OP.
Giải pháp nhanh cho Java bằng cách sử dụng một ArrayList
và HashMap
[phần tử -> chỉ mục].
Động lực: Tôi cần một tập hợp các mục có RandomAccess
thuộc tính, đặc biệt là chọn một mục ngẫu nhiên từ tập hợp (xem pollRandom
phương thức). Điều hướng ngẫu nhiên trong cây nhị phân là không chính xác: cây không cân bằng hoàn hảo, điều này sẽ không dẫn đến phân phối đồng đều.
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
là thực sự an toàn, những cái được bọc bằng Collections.synchronized()
bán an toàn. Ngoài ra, OP không nói gì về đồng thời nên đây là câu trả lời hợp lệ và tốt.
dta
( Iterators.unmodifiableIterator
ví dụ có thể đạt được thông qua ổi ). Mặt khác, các cài đặt mặc định của ví dụ: remove ALL và keep ALL trong Tóm tắt và cha mẹ của nó làm việc với iterator đó sẽ làm rối tung bạn RandomSet
!
Điều này nhanh hơn vòng lặp for-every trong câu trả lời được chấp nhận:
int index = rand.nextInt(set.size());
Iterator<Object> iter = set.iterator();
for (int i = 0; i < index; i++) {
iter.next();
}
return iter.next();
Cấu trúc for- Iterator.hasNext()
every gọi trên mỗi vòng lặp, nhưng vì index < set.size()
, kiểm tra đó là chi phí không cần thiết. Tôi thấy tốc độ tăng 10-20%, nhưng YMMV. (Ngoài ra, phần này biên dịch mà không cần phải thêm một tuyên bố trả lại.)
Lưu ý rằng mã này (và hầu hết các câu trả lời khác) có thể được áp dụng cho bất kỳ Bộ sưu tập nào, không chỉ Set. Ở dạng phương thức chung:
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();
}
}
Nếu bạn muốn làm điều đó trong Java, bạn nên xem xét việc sao chép các phần tử vào một loại bộ sưu tập truy cập ngẫu nhiên nào đó (chẳng hạn như ArrayList). Bởi vì, trừ khi bộ của bạn nhỏ, việc truy cập phần tử được chọn sẽ tốn kém (O (n) thay vì O (1)). [ed: danh sách sao chép cũng là O (n)]
Ngoài ra, bạn có thể tìm kiếm một triển khai Set khác phù hợp hơn với yêu cầu của bạn. Các ListOrderedSet từ Commons Collections trông đầy hứa hẹn.
Trong Java 8:
static <E> E getRandomSetElement(Set<E> set) {
return set.stream().skip(new Random().nextInt(set.size())).findFirst().orElse(null);
}
Trong 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);
Điều này giống hệt với câu trả lời được chấp nhận (Khoth), nhưng với những biến không cần thiết size
và i
bị loại bỏ.
int random = new Random().nextInt(myhashSet.size());
for(Object obj : myhashSet) {
if (random-- == 0) {
return obj;
}
}
Mặc dù loại bỏ hai biến đã nói ở trên, giải pháp trên vẫn là ngẫu nhiên vì chúng tôi dựa vào ngẫu nhiên (bắt đầu từ một chỉ số được chọn ngẫu nhiên) để tự giảm dần 0
qua mỗi lần lặp.
if (--random < 0) {
, nơi random
đạt đến -1
.
Dung dịch clojure:
(defn pick-random [set] (let [sq (seq set)] (nth sq (rand-int (count sq)))))
nth
phần tử, bạn cũng phải duyệt qua phần tử seq
.
C ++. Điều này sẽ nhanh chóng hợp lý, vì nó không yêu cầu lặp lại trên toàn bộ hoặc sắp xếp nó. Điều này sẽ hoạt động tốt với hầu hết các trình biên dịch hiện đại, giả sử chúng hỗ trợ tr1 . Nếu không, bạn có thể cần sử dụng Boost.
Tài liệu Boost rất hữu ích ở đây để giải thích điều này, ngay cả khi bạn không sử dụng Boost.
Bí quyết là sử dụng thực tế là dữ liệu đã được chia thành các nhóm và nhanh chóng xác định một nhóm được chọn ngẫu nhiên (với xác suất phù hợp).
//#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;
}
}
Giải pháp trên nói về độ trễ nhưng không đảm bảo xác suất bằng nhau của mỗi chỉ số được chọn.
Nếu cần phải xem xét, hãy thử lấy mẫu hồ chứa. http://en.wikipedia.org/wiki/Reservoir_sampling .
Bộ sưu tập.shuffle () (như được đề xuất bởi vài người) sử dụng một thuật toán như vậy.
Vì bạn đã nói "Giải pháp cho các ngôn ngữ khác cũng được hoan nghênh", đây là phiên bản dành cho Python:
>>> import random
>>> random.choice([1,2,3,4,5,6])
3
>>> random.choice([1,2,3,4,5,6])
4
Bạn không thể lấy kích thước / chiều dài của tập hợp / mảng, tạo một số ngẫu nhiên trong khoảng từ 0 đến kích thước / chiều dài, sau đó gọi phần tử có chỉ số khớp với số đó? Hashset có phương thức .size (), tôi khá chắc chắn.
Trong psuedocode -
function randFromSet(target){
var targetLength:uint = target.length()
var randomIndex:uint = random(0,targetLength);
return target[randomIndex];
}
PHP, giả sử "set" là một mảng:
$foo = array("alpha", "bravo", "charlie");
$index = array_rand($foo);
$val = $foo[$index];
Các hàm Mersenne Twister tốt hơn nhưng không có MT tương đương với mảng_rand trong PHP.
Biểu tượng có loại tập hợp và toán tử phần tử ngẫu nhiên, đơn nguyên "?", Vì vậy biểu thức
? set( [1, 2, 3, 4, 5] )
sẽ tạo ra một số ngẫu nhiên trong khoảng từ 1 đến 5.
Hạt giống ngẫu nhiên được khởi tạo thành 0 khi một chương trình được chạy, do đó, để tạo ra các kết quả khác nhau trên mỗi lần sử dụng chạy randomize()
Trong 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"]);
Giải pháp Javascript;)
function choose (set) {
return set[Math.floor(Math.random() * set.length)];
}
var set = [1, 2, 3, 4], rand = choose (set);
Hay cách khác:
Array.prototype.choose = function () {
return this[Math.floor(Math.random() * this.length)];
};
[1, 2, 3, 4].choose();
Trong Mathicala:
a = {1, 2, 3, 4, 5}
a[[ ⌈ Length[a] Random[] ⌉ ]]
Hoặc, trong các phiên bản gần đây, chỉ cần:
RandomChoice[a]
Điều này đã nhận được một phiếu bầu xuống, có lẽ vì nó thiếu lời giải thích, vì vậy đây là:
Random[]
tạo ra một giả ngẫu nhiên nổi giữa 0 và 1. Điều này được nhân với độ dài của danh sách và sau đó hàm trần được sử dụng để làm tròn đến số nguyên tiếp theo. Chỉ số này sau đó được trích xuất từ a
.
Vì chức năng bảng băm thường được thực hiện với các quy tắc trong Mathematica và các quy tắc được lưu trữ trong danh sách, nên người ta có thể sử dụng:
a = {"Badger" -> 5, "Bird" -> 1, "Fox" -> 3, "Frog" -> 2, "Wolf" -> 4};
Thế còn
public static <A> A getRandomElement(Collection<A> c, Random r) {
return new ArrayList<A>(c).get(r.nextInt(c.size()));
}
Để giải trí, tôi đã viết một RandomHashset dựa trên lấy mẫu từ chối. Hơi khó khăn một chút, vì HashMap không cho phép chúng tôi truy cập trực tiếp vào bảng, nhưng nó sẽ hoạt động tốt.
Nó không sử dụng bất kỳ bộ nhớ bổ sung nào và thời gian tra cứu là O (1) được khấu hao. (Vì java HashTable dày đặc).
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;
}
}
}
Đơn giản nhất với Java 8 là:
outbound.stream().skip(n % outbound.size()).findFirst().get()
trong đó n
một số nguyên ngẫu nhiên. Tất nhiên nó có hiệu suất thấp hơn so vớifor(elem: Col)
Với Guava, chúng ta có thể làm tốt hơn một chút so với câu trả lời của Khoth:
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);
}
Nếu bạn thực sự chỉ muốn chọn "bất kỳ" đối tượng nào từ Set
, mà không có bất kỳ sự đảm bảo nào về tính ngẫu nhiên, thì dễ nhất là lấy cái đầu tiên được trả về bởi iterator.
Set<Integer> s = ...
Iterator<Integer> it = s.iterator();
if(it.hasNext()){
Integer i = it.next();
// i is a "random" object from set
}
Một giải pháp chung sử dụng câu trả lời của Khoth làm điểm khởi đầu.
/**
* @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;
}
Thật không may, điều này không thể được thực hiện một cách hiệu quả (tốt hơn O (n)) trong bất kỳ bộ chứa Thư viện chuẩn nào.
Điều này thật kỳ lạ, vì rất dễ dàng để thêm một hàm chọn ngẫu nhiên vào các bộ băm cũng như các bộ nhị phân. Trong bộ băm không thưa thớt, bạn có thể thử các mục ngẫu nhiên, cho đến khi bạn nhận được một cú đánh. Đối với cây nhị phân, bạn có thể chọn ngẫu nhiên giữa cây con trái hoặc phải, với tối đa các bước O (log2). Tôi đã thực hiện một bản demo của phần sau bên dưới:
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
Tôi nhận [995, 975, 971, 995, 1057, 1004, 966, 1052, 984, 1001] làm đầu ra, vì vậy các đường nối phân phối tốt.
Tôi đã phải vật lộn với cùng một vấn đề cho chính mình và tôi vẫn chưa quyết định thời tiết hiệu suất của lựa chọn hiệu quả hơn này đáng để sử dụng bộ sưu tập dựa trên python. Tất nhiên tôi có thể tinh chỉnh nó và dịch nó sang C, nhưng đó là quá nhiều công việc cho tôi ngày hôm nay :)