Wybór losowego elementu z zestawu

180

Jak wybrać losowy element z zestawu? Szczególnie interesuje mnie wybranie losowego elementu z HashSet lub LinkedHashSet w Javie. Mile widziane są również rozwiązania dla innych języków.

Bezradny
źródło
5
Powinieneś określić kilka warunków, aby zobaczyć, czy naprawdę tego chcesz. - W jakich czasach będziesz wybierać losowy element? - Czy dane muszą być przechowywane w HashSet lub LinkedHashSet, ani nie są dostępne losowo. - Czy hasz jest duży? Czy klucze są małe?
David Nehme,

Odpowiedzi:

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++;
}
Khoth
źródło
94
Jeśli myHashSet jest duży, będzie to raczej powolne rozwiązanie, ponieważ średnio potrzeba (n / 2) iteracji, aby znaleźć losowy obiekt.
daniel
6
jeśli twoje dane są w zestawie skrótów, potrzebujesz O (n) czasu. Nie da się tego obejść, jeśli wybierasz tylko jeden element, a dane są przechowywane w HashSet.
David Nehme,
8
@David Nehme: Jest to wada specyfikacji HashSet w Javie. W C ++ typowe jest, aby mieć bezpośredni dostęp do zasobników, które tworzą hashset, co pozwala nam efektywniej wybierać losowe elementy. Jeśli w Javie potrzebne są elementy losowe, warto zdefiniować niestandardowy zestaw skrótów, który pozwoli użytkownikowi zajrzeć pod maskę. Zobacz [boost's docs] [1], aby uzyskać więcej informacji na ten temat. [1] boost.org/doc/libs/1_43_0/doc/html/unordered/buckets.html
Aaron McDaid,
11
Jeśli zestaw nie jest mutowany przy wielu dostępach, możesz skopiować go do tablicy, a następnie uzyskać dostęp do O (1). Po prostu użyj myHashSet.toArray ()
ykaganovich
2
@ykaganovich czy to nie pogorszy sprawy, skoro zestaw musiałby zostać skopiowany do nowej tablicy? docs.oracle.com/javase/7/docs/api/java/util/… „ta metoda musi przydzielić nową tablicę, nawet jeśli ta kolekcja jest
obsługiwana
73

Nieco pokrewne Czy wiesz, że:

Istnieją przydatne metody java.util.Collectionstasowania całych kolekcji: Collections.shuffle(List<?>)i Collections.shuffle(List<?> list, Random rnd).

ciastko z kurczaka
źródło
Niesamowite! Nie ma tam żadnych odwołań krzyżowych w dokumencie java! Podobnie jak random.shuffle ()
smci,
25
Ale działa to tylko z Listami, tj. Strukturami, które mają funkcję .get ().
bourbaki4481472
4
@ bourbaki4481472 jest absolutnie poprawne. Działa to tylko dla tych kolekcji rozszerzających Listinterfejs, a nie dla Setinterfejsu omawianego przez OP.
Thomas
31

Szybkie rozwiązanie dla języka Java za pomocą znaków ArrayListi HashMap: [element -> indeks].

Motywacja: potrzebowałem zestawu przedmiotów z RandomAccesswłaściwościami, zwłaszcza aby wybrać losowy przedmiot z zestawu (patrz pollRandommetoda). Losowa nawigacja w drzewie binarnym nie jest dokładna: drzewa nie są idealnie zrównoważone, co nie prowadziłoby do równomiernego rozmieszczenia.

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();
    }
}
fandrew
źródło
Cóż, to by działało, ale pytanie dotyczyło interfejsu Set. To rozwiązanie zmusza użytkowników do posiadania konkretnych referencji typu RandomSet.
Johan Tidén
Naprawdę podoba mi się to rozwiązanie, ale nie jest bezpieczne wątkowo, mogą wystąpić niedokładności między mapą a listą, więc dodałbym kilka zsynchronizowanych bloków
Kostas Chalkias
@KonstantinosChalkias wbudowane kolekcje również nie są bezpieczne wątkowo. Tylko te z nazwą Concurrentsą naprawdę bezpieczne, te owinięte Collections.synchronized()są pół-bezpieczne. Również OP nie powiedział nic o współbieżności, więc jest to poprawna i dobra odpowiedź.
TWiStErRob
Iterator zwrócony tutaj nie powinien być w stanie usunąć elementów z dta(można to osiągnąć Iterators.unmodifiableIteratorna przykład za pomocą guawy ). W przeciwnym razie domyślne implementacje, np. RemoveAll i retainAll w AbstractSet i jego elementy nadrzędne pracujące z tym iteratorem, zepsują RandomSet!
wyemitowano
Niezłe rozwiązanie. W rzeczywistości możesz użyć drzewa, jeśli każdy węzeł zawiera liczbę węzłów w poddrzewie, z którego pochodzi. Następnie oblicz losową liczbę rzeczywistą w zakresie 0..1 i podejmij ważoną 3-kierunkową decyzję (wybierz bieżący węzeł lub zejdź do lewego lub prawego poddrzewa) w każdym węźle na podstawie liczby węzłów. Ale imo Twoje rozwiązanie jest o wiele przyjemniejsze.
Gene
30

Jest to szybsze niż pętla for-each w akceptowanej odpowiedzi:

int index = rand.nextInt(set.size());
Iterator<Object> iter = set.iterator();
for (int i = 0; i < index; i++) {
    iter.next();
}
return iter.next();

Konstrukcja for-each wywołuje Iterator.hasNext()każdą pętlę, ale ponieważ index < set.size()to sprawdzenie jest niepotrzebnym narzutem. Widziałem wzrost prędkości o 10-20%, ale YMMV. (Ponadto kompiluje się bez konieczności dodawania dodatkowej instrukcji return).

Zwróć uwagę, że ten kod (i większość innych odpowiedzi) można zastosować do dowolnej kolekcji, a nie tylko do zestawu. W ogólnej formie metody:

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();
    }
}
Sean Van Gorder
źródło
15

Jeśli chcesz to zrobić w Javie, powinieneś rozważyć skopiowanie elementów do jakiejś kolekcji o swobodnym dostępie (takiej jak ArrayList). Ponieważ, chyba że twój zestaw jest mały, dostęp do wybranego elementu będzie kosztowny (O (n) zamiast O (1)). [ed: kopia listy jest również O (n)]

Alternatywnie możesz poszukać innej implementacji zestawu, która bardziej odpowiada Twoim wymaganiom. Zestaw ListOrderedSet z Commons Collections wygląda obiecująco.

Dan Dyer
źródło
8
Kopiowanie do listy będzie kosztować O (n) czasu, a także zużyje O (n) pamięci, więc dlaczego miałoby to być lepszym wyborem niż bezpośrednie pobieranie z mapy?
mdma
12
Zależy to od tego, ile razy chcesz wybierać z zestawu. Kopiowanie jest jednorazową operacją, a następnie możesz wybierać z zestawu tyle razy, ile potrzebujesz. Jeśli wybierasz tylko jeden element, to tak, kopia nie przyspiesza rzeczy.
Dan Dyer,
To tylko jednorazowa operacja, jeśli chcesz mieć możliwość wybierania z powtórzeniami. Jeśli chcesz, aby wybrana pozycja została usunięta z zestawu, wrócisz do O (n).
TurnipEntropy
12

W Javie 8:

static <E> E getRandomSetElement(Set<E> set) {
    return set.stream().skip(new Random().nextInt(set.size())).findFirst().orElse(null);
}
Joshua Bone
źródło
9

W Javie:

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())]);
}
Jorge Ferreira
źródło
11
Twoja odpowiedź działa, ale nie jest zbyt wydajna ze względu na część set.toArray ().
Clue Less,
12
należy przenieść toArray poza pętlę.
David Nehme,
8
List asList = new ArrayList(mySet);
Collections.shuffle(asList);
return asList.get(0);
Ben Noland
źródło
21
To jest strasznie nieefektywne. Konstruktor ArrayList wywołuje .toArray () na podanym zestawie. ToArray (w większości, jeśli nie we wszystkich standardowych implementacjach kolekcji) wykonuje iterację po całej kolekcji, wypełniając tablicę na bieżąco. Następnie tasujesz listę, co powoduje zamianę każdego elementu na losowy. Byłoby znacznie lepiej, gdybyś po prostu iterował zestaw do losowego elementu.
Chris Bode
4

Jest to identyczne z zaakceptowaną odpowiedzią (Khoth), ale z niepotrzebną sizei usuniętymi i zmiennymi.

    int random = new Random().nextInt(myhashSet.size());
    for(Object obj : myhashSet) {
        if (random-- == 0) {
            return obj;
        }
    }

Pomimo rezygnacji z dwóch wyżej wymienionych zmiennych, powyższe rozwiązanie nadal pozostaje przypadkowe, ponieważ polegamy na losowym (zaczynającym się od losowo wybranym indeksie), aby zmniejszyć się w kierunku 0każdej iteracji.

Jason Hartley
źródło
1
Trzecia linia może być również tam if (--random < 0) {, gdzie randomsięga -1.
Salvador
3

Rozwiązanie Clojure:

(defn pick-random [set] (let [sq (seq set)] (nth sq (rand-int (count sq)))))
pjb3
źródło
1
To rozwiązanie jest również liniowe, ponieważ aby dostać się do nthelementu, musisz również przejść przez niego seq.
Bruno Kim
1
Jest też liniowa, bo ładnie mieści się w jednej linii: D
Krzysztof Wolny
2

Perl 5

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

Oto jeden sposób, aby to zrobić.

JJ
źródło
2

C ++. Powinno to być dość szybkie, ponieważ nie wymaga iterowania po całym zestawie ani sortowania go. Powinno to działać po wyjęciu z pudełka z większością nowoczesnych kompilatorów, zakładając, że obsługują one tr1 . Jeśli nie, może być konieczne użycie funkcji Boost.

Dokumentacja Boost są tutaj pomocne, aby to wyjaśnić, nawet jeśli nie używasz Boost.

Sztuczka polega na wykorzystaniu faktu, że dane zostały podzielone na kosze i szybko zidentyfikować losowo wybrany kosz (z odpowiednim prawdopodobieństwem).

//#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;
  }
}
Aaron McDaid
źródło
2

Powyższe rozwiązanie mówi w kategoriach opóźnienia, ale nie gwarantuje równego prawdopodobieństwa wybrania każdego indeksu.
Jeśli trzeba to wziąć pod uwagę, spróbuj pobrać próbki ze zbiornika. http://en.wikipedia.org/wiki/Reservoir_sampling .
Collections.shuffle () (jak sugeruje niewielu) używa jednego z takich algorytmów.

tempo
źródło
1

Ponieważ powiedziałeś „Mile widziane są również rozwiązania dla innych języków”, oto wersja dla Pythona:

>>> import random
>>> random.choice([1,2,3,4,5,6])
3
>>> random.choice([1,2,3,4,5,6])
4
Swaroop CH
źródło
3
Tyle, że [1, 2, 3, 4, 5, 6] nie jest zbiorem, ale listą, ponieważ nie obsługuje takich rzeczy jak szybkie wyszukiwanie.
Thomas Ahle
Nadal możesz: >>> random.choice (list (set (range (5)))) >>> 4 Nie jest to idealne, ale wystarczy, jeśli absolutnie tego potrzebujesz.
Sapphire
1

Nie możesz po prostu uzyskać rozmiaru / długości zestawu / tablicy, wygenerować losową liczbę od 0 do rozmiaru / długości, a następnie wywołać element, którego indeks pasuje do tej liczby? HashSet ma metodę .size (), jestem prawie pewien.

W psuedokodzie -

function randFromSet(target){
 var targetLength:uint = target.length()
 var randomIndex:uint = random(0,targetLength);
 return target[randomIndex];
}
matowy lohkamp
źródło
Działa to tylko wtedy, gdy dany kontener obsługuje losowe wyszukiwanie indeksu. Wiele implementacji kontenerów tego nie robi (np. Tabele skrótów, drzewa binarne, połączone listy).
David Haley
1

PHP, zakładając, że „zestaw” jest tablicą:

$foo = array("alpha", "bravo", "charlie");
$index = array_rand($foo);
$val = $foo[$index];

Funkcje Mersenne Twister są lepsze, ale w PHP nie ma odpowiednika MT dla array_rand.

dirtside
źródło
Większość implementacji zestawów nie ma operatora get (i) ani indeksowania, więc id załóżmy, że dlatego OP określił swój zestaw
DownloadPizza
1

Ikona ma ustawiony typ i operator elementu losowego, jednoargumentowy „?”, Czyli wyrażenie

? set( [1, 2, 3, 4, 5] )

wygeneruje losową liczbę od 1 do 5.

Losowe ziarno jest inicjowane na 0, gdy program jest uruchamiany, aby uzyskać różne wyniki przy każdym uruchomieniu randomize()

Hugh Allen
źródło
1

W 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"]);
Mitch Wheat
źródło
wygląda na to, że odrzucili głosy, ponieważ do kiepskiego słownika java (lub tak zwanego LinkedHashSet, cokolwiek to jest, do diabła) nie można uzyskać „losowego dostępu” (do którego można uzyskać dostęp za pomocą klucza). Gówno java tak bardzo mnie rozśmiesza
Federico Berasategui
1

Rozwiązanie Javascript;)

function choose (set) {
    return set[Math.floor(Math.random() * set.length)];
}

var set  = [1, 2, 3, 4], rand = choose (set);

Lub alternatywnie:

Array.prototype.choose = function () {
    return this[Math.floor(Math.random() * this.length)];
};

[1, 2, 3, 4].choose();
Mathew Byrne
źródło
Wolę drugą możliwość. :-)
marcospereira
ooh, lubię rozszerzać dodawanie nowej metody tablicowej!
Matt Lohkamp,
1

W seplenieniu

(defun pick-random (set)
       (nth (random (length set)) set))
inglesp
źródło
Działa to tylko w przypadku list, prawda? Dzięki ELTtemu może pracować dla dowolnej sekwencji.
Ken,
1

W Mathematica:

a = {1, 2, 3, 4, 5}

a[[  Length[a] Random[]  ]]

Lub, w najnowszych wersjach, po prostu:

RandomChoice[a]

Ta otrzymała głos negatywny, być może dlatego, że brakuje jej wyjaśnienia, więc oto jedno:

Random[]generuje pseudolosową liczbę zmiennoprzecinkową z przedziału od 0 do 1. Jest to mnożone przez długość listy, a następnie funkcja sufitu jest używana do zaokrąglania w górę do następnej liczby całkowitej. Ten indeks jest następnie wyodrębniany z a.

Ponieważ funkcja tablicy skrótów jest często wykonywana za pomocą reguł w Mathematica, a reguły są przechowywane na listach, można użyć:

a = {"Badger" -> 5, "Bird" -> 1, "Fox" -> 3, "Frog" -> 2, "Wolf" -> 4};
Panie Wizard
źródło
1

Może po prostu

public static <A> A getRandomElement(Collection<A> c, Random r) {
  return new ArrayList<A>(c).get(r.nextInt(c.size()));
}
Daniel Lubarov
źródło
1

Dla zabawy napisałem RandomHashSet na podstawie próbkowania odrzucania. To trochę hacky, ponieważ HashMap nie pozwala nam uzyskać bezpośredniego dostępu do jego tabeli, ale powinno działać dobrze.

Nie używa dodatkowej pamięci, a czas wyszukiwania jest amortyzowany O (1). (Ponieważ java HashTable jest gęsty).

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;
        }
    }
}
Thomas Ahle
źródło
1
Dokładnie to, o czym myślałem! Najlepsza odpowiedź!
mmm
Właściwie wracając do tego, myślę, że to nie jest całkiem jednolite, jeśli hashmap ma wiele kolizji i robimy wiele zapytań. Dzieje się tak, ponieważ hashmap java używa segmentów / łańcuchów, a ten kod zawsze zwróci pierwszy element w określonym zasobniku. Nadal jednak jesteśmy jednorodni co do losowości funkcji skrótu.
Thomas Ahle,
1

Najłatwiejszy z Javą 8 to:

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

gdzie njest losową liczbą całkowitą. Oczywiście ma mniejszą wydajność niż w przypadkufor(elem: Col)

Nicu Marasoiu
źródło
1

Z guawą możemy zrobić trochę lepiej niż odpowiedź Khotha:

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);
}
dimo414
źródło
0

PHP, używając 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];
da5id
źródło
0

możesz również przenieść zestaw do tablicy użyj tablicy to prawdopodobnie zadziała na małą skalę Widzę pętlę for w najczęściej głosowanej odpowiedzi i tak O (n)

Object[] arr = set.toArray();

int v = (int) arr[rnd.nextInt(arr.length)];
sivi
źródło
0

Jeśli naprawdę chcesz po prostu wybrać „dowolny” obiekt z elementu Set, bez żadnych gwarancji co do losowości, najłatwiej jest wziąć pierwszy zwrócony przez iterator.

    Set<Integer> s = ...
    Iterator<Integer> it = s.iterator();
    if(it.hasNext()){
        Integer i = it.next();
        // i is a "random" object from set
    }
Philipp
źródło
1
Nie będzie to jednak losowy wybór. Wyobraź sobie wielokrotne wykonywanie tej samej operacji na tym samym zestawie. Myślę, że kolejność będzie taka sama.
Menezes Sousa
0

Ogólne rozwiązanie wykorzystujące odpowiedź Khotha jako punkt wyjścia.

/**
 * @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;
}
stivlo
źródło
0

Niestety, nie można tego zrobić wydajnie (lepiej niż O (n)) w żadnym z kontenerów zestawu biblioteki standardowej.

Jest to dziwne, ponieważ bardzo łatwo jest dodać losową funkcję wybierania zarówno do zestawów haszujących, jak i binarnych. W niezbyt rzadkim zestawie skrótów możesz próbować losowych wpisów, aż uzyskasz trafienie. W przypadku drzewa binarnego można losowo wybierać między lewym lub prawym poddrzewem, z maksymalną liczbą O (log2) kroków. Zaimplementowałem demo poniżej:

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

Otrzymałem [995, 975, 971, 995, 1057, 1004, 966, 1052, 984, 1001] jako wyjście, więc rozkład wydaje się dobry.

Sam zmagałem się z tym samym problemem i nie zdecydowałem jeszcze, czy wzrost wydajności tego bardziej wydajnego wyboru jest wart kosztów narzutów związanych z używaniem kolekcji opartej na Pythonie. Mógłbym to oczywiście udoskonalić i przetłumaczyć na C, ale to dla mnie dziś za dużo pracy :)

Thomas Ahle
źródło
1
Myślę, że powodem, dla którego nie jest to zaimplementowane w drzewie binarnym, jest to, że taka metoda nie wybierałaby elementów w sposób jednolity. Ponieważ są to węzły bez lewych / prawych dzieci, może wystąpić sytuacja, w której lewe dziecko zawiera więcej elementów niż prawe dziecko (lub odwrotnie), co spowodowałoby, że wybranie elementu po prawej (lub lewej) stronie dziecka byłoby bardziej prawdopodobne.
Willem Van Onsem
1
@CommuSoft: Dlatego przechowuję rozmiar każdego poddrzewa, więc mogę wybrać moje prawdopodobieństwa na podstawie tych.
Thomas Ahle