Chọn một yếu tố ngẫu nhiên từ một bộ


180

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.


5
Bạn nên chỉ định một số điều kiện để xem nếu đây thực sự là những gì bạn muốn. - Làm thế nào nhiều lần bạn sẽ được chọn một yếu tố ngẫu nhiên? - Dữ liệu có cần được lưu trữ trong Hashset hoặc LinkedHashset không, không thể truy cập ngẫu nhiên. - Bộ băm có lớn không? Các phím có nhỏ không?
David Nehme

Câu trả lời:


88
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++;
}

94
Nếu myHashSet lớn, thì đây sẽ là một giải pháp khá chậm vì trung bình, sẽ cần (n / 2) các lần lặp để tìm đối tượng ngẫu nhiên.
daniel

6
nếu dữ liệu của bạn nằm trong bộ băm, bạn cần thời gian O (n). Không có cách nào khác nếu bạn chỉ chọn một yếu tố duy nhất và dữ liệu được lưu trữ trong Hashset.
David Nehme

8
@David Nehme: Đây là một nhược điểm trong đặc tả của Hashset trong Java. Trong C ++, điển hình là có thể truy cập trực tiếp vào các nhóm tạo nên bộ băm, cho phép chúng ta chọn một yếu tố ngẫu nhiên hiệu quả hơn. Nếu các yếu tố ngẫu nhiên là cần thiết trong Java, có thể đáng để xác định một bộ băm tùy chỉnh cho phép người dùng nhìn dưới mui xe. Xem [tài liệu của boost] [1] để biết thêm một chút về điều này. [1] boost.org/doc/libs/1_43_0/doc/html/unordered/buckets.html
Aaron McDaid

11
Nếu tập hợp không bị đột biến qua nhiều lần truy cập, bạn có thể sao chép nó vào một mảng và sau đó truy cập O (1). Chỉ cần sử dụng myHashSet.toArray ()
ykaganovich

2
@ykaganovich điều này sẽ không làm cho vấn đề tồi tệ hơn, vì bộ này sẽ phải được sao chép sang một mảng mới? docs.oracle.com/javase/7/docs/api/java/util/ triệt "phương pháp này phải phân bổ một mảng mới ngay cả khi bộ sưu tập này được hỗ trợ bởi một mảng"
anton1980

73

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<?>)Collections.shuffle(List<?> list, Random rnd).


Tuyệt vời! Điều này không được đặt chéo ở bất cứ đâu trong tài liệu java! Giống như
ngẫu nhiên của Python.shuffle

25
Nhưng điều này chỉ hoạt động với Danh sách, tức là các cấu trúc có hàm .get ().
bourbaki4481472

4
@ bourbaki4481472 là hoàn toàn chính xác. Điều này chỉ hoạt động cho những bộ sưu tập mở rộng Listgiao diện, không phải Setgiao diện được thảo luận bởi OP.
Thomas

31

Giải pháp nhanh cho Java bằng cách sử dụng một ArrayListHashMap[phần tử -> chỉ mục].

Động lực: Tôi cần một tập hợp các mục có RandomAccessthuộc tính, đặc biệt là chọn một mục ngẫu nhiên từ tập hợp (xem pollRandomphươ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();
    }
}

Vâng, nó sẽ hoạt động nhưng câu hỏi là về giao diện Set. Giải pháp này buộc người dùng phải có các tham chiếu loại cụ thể của Randomset.
Johan Tidén

Tôi thực sự thích giải pháp này, nhưng nó không an toàn, không chính xác giữa Bản đồ và Danh sách có thể xảy ra, vì vậy tôi sẽ thêm một số khối được đồng bộ hóa
Kostas Chalkias

@KonstantinosChalkias các bộ sưu tập tích hợp cũng không phải là chủ đề an toàn. Chỉ những cái có tên Concurrentlà 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.
TWiStErRob

Trình lặp được trả về ở đây sẽ không thể xóa các phần tử khỏi dta( Iterators.unmodifiableIteratorví 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!
bịt miệng

Giải pháp tốt đẹp. Bạn thực sự có thể sử dụng một cây nếu mỗi nút chứa số lượng nút trong cây con nó gốc. Sau đó tính toán một số thực ngẫu nhiên trong 0..1 và đưa ra quyết định 3 chiều có trọng số (chọn nút hiện tại hoặc hạ xuống thành cây con trái hoặc phải) tại mỗi nút dựa trên số lượng nút. Nhưng imo giải pháp của bạn đẹp hơn nhiều.
Gene

29

Đ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();
    }
}

15

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.


8
Sao chép vào danh sách sẽ tốn O (n) kịp thời và cũng sử dụng bộ nhớ O (n), vậy tại sao đó sẽ là lựa chọn tốt hơn so với tìm nạp trực tiếp từ bản đồ?
mdma

12
Nó phụ thuộc vào số lần bạn muốn chọn từ bộ. Bản sao là một hoạt động một lần và sau đó bạn có thể chọn từ bộ nhiều lần bạn cần. Nếu bạn chỉ chọn một yếu tố, thì có, bản sao sẽ không khiến mọi thứ nhanh hơn.
Dan Dyer

Đây chỉ là thao tác một lần nếu bạn muốn có thể chọn với sự lặp lại. Nếu bạn muốn xóa mục đã chọn khỏi tập hợp, thì bạn sẽ quay lại O (n).
TurnipEntropy

12

Trong Java 8:

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

9

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())]);
}

11
Câu trả lời của bạn hoạt động, nhưng nó không hiệu quả lắm vì phần set.toArray ().
mối ít hơn

12
bạn nên di chuyển toArray ra bên ngoài vòng lặp.
David Nehme

8
List asList = new ArrayList(mySet);
Collections.shuffle(asList);
return asList.get(0);

21
Điều này là không hiệu quả. Hàm tạo ArrayList của bạn gọi .toArray () trên tập được cung cấp. ToArray (trong hầu hết nếu không phải tất cả các triển khai bộ sưu tập tiêu chuẩn) lặp lại trên toàn bộ bộ sưu tập, điền vào một mảng khi nó đi. Sau đó, bạn xáo trộn danh sách, trong đó hoán đổi từng phần tử với một phần tử ngẫu nhiên. Bạn sẽ tốt hơn nhiều khi chỉ cần lặp lại tập hợp thành một yếu tố ngẫu nhiên.
Chris Bode

4

Đ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 sizeibị 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 0qua mỗi lần lặp.


1
Dòng thứ ba cũng có thể if (--random < 0) {, nơi randomđạt đến -1.
Salvador

3

Dung dịch clojure:

(defn pick-random [set] (let [sq (seq set)] (nth sq (rand-int (count sq)))))

1
Giải pháp này cũng là tuyến tính, bởi vì để có được nthphần tử, bạn cũng phải duyệt qua phần tử seq.
Bruno Kim

1
Nó cũng tuyến tính vì nó phù hợp độc đáo trong một dòng: D
Krzysztof Wolny

2

Perl 5

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

Đây là một cách để làm điều đó.


2

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;
  }
}

2

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.


1

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

3
Chỉ, [1,2,3,4,5,6] không phải là một bộ, mà là một danh sách, vì nó không hỗ trợ những thứ như tra cứu nhanh.
Thomas Ahle

Bạn vẫn có thể làm: >>> Random.choice (danh sách (bộ (phạm vi (5)))) >>> 4 Không lý tưởng nhưng nó sẽ làm nếu bạn thực sự cần.
SapphireSun

1

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];
}

Điều này chỉ hoạt động nếu container trong câu hỏi hỗ trợ tra cứu chỉ mục ngẫu nhiên. Nhiều triển khai vùng chứa không (ví dụ: bảng băm, cây nhị phân, danh sách được liên kết).
David Haley

1

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.


Hầu hết các cài đặt triển khai không có toán tử get (i) hoặc lập chỉ mục, vì vậy id cho rằng đó là lý do tại sao OP chỉ định một tập hợp
DownloadPizza

1

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()


1

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"]);

có vẻ như họ bị hạ cấp bởi vì từ điển java xảo quyệt (hay còn gọi là LinkedHashset, dù đó là địa ngục nào) không thể được "truy cập ngẫu nhiên" (tôi đang truy cập bằng khóa, tôi đoán vậy). Crap java làm tôi cười rất nhiều
Federico Berasargetui

1

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();

Tôi thích sự thay thế thứ hai. :-)
marcospereira

ooh, tôi thích mở rộng thêm phương thức mảng mới!
matt lohkamp

1

Trong lisp

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

Điều này chỉ hoạt động cho danh sách, phải không? Với ELTnó có thể làm việc cho bất kỳ trình tự.
Ken

1

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};

1

Thế còn

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

1

Để 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;
        }
    }
}

1
Chính xác những gì tôi đã nghĩ! Câu trả lời tốt nhất!
mmm

Trên thực tế, trở lại với nó, tôi đoán điều này không hoàn toàn thống nhất, nếu hashmap có nhiều va chạm và chúng tôi thực hiện nhiều truy vấn. Đó là bởi vì hashmap java sử dụng các nhóm / chuỗi và mã này sẽ luôn trả về phần tử đầu tiên trong nhóm cụ thể. Chúng tôi vẫn thống nhất về tính ngẫu nhiên của hàm băm.
Thomas Ahle

1

Đơn giản nhất với Java 8 là:

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

trong đó nmộ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)


1

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);
}

0

PHP, sử dụng 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

bạn cũng có thể chuyển tập hợp thành mảng sử dụng mảng, nó có thể sẽ hoạt động ở quy mô nhỏ. Tôi thấy vòng lặp for trong câu trả lời được bình chọn nhiều nhất là O (n)

Object[] arr = set.toArray();

int v = (int) arr[rnd.nextInt(arr.length)];

0

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
    }

1
Điều này sẽ không phải là một lựa chọn ngẫu nhiên mặc dù. Hãy tưởng tượng thực hiện cùng một thao tác trên cùng một bộ nhiều lần. Tôi nghĩ rằng thứ tự sẽ giống nhau.
Menezes Sousa

0

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;
}

0

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 :)


1
Một lý do tôi nghĩ rằng điều này không được thực hiện trong cây nhị phân là phương pháp như vậy sẽ không chọn các mục một cách thống nhất. Vì chúng là các nút không có con trái / phải, nên tình huống có thể xảy ra khi trẻ trái chứa nhiều vật phẩm hơn trẻ phải (hoặc ngược lại), điều này sẽ khiến việc chọn một vật phẩm ở trẻ phải (hoặc trái), có thể xảy ra hơn.
Willem Van Onsem

1
@CommuSoft: Đó là lý do tại sao tôi lưu trữ kích thước của mỗi cây con, vì vậy tôi có thể chọn xác suất của mình dựa trên những xác suất đó.
Thomas Ahle
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.