Idea jest bardzo prosta - czasem potrzebujemy wybrać losowy element z tablicy, ale chcemy żeby prawdopodobieństwo wyboru pierwszego elementu wynosiło np. 0,6, drugiego 0,3, a trzeciego 0,1.
Taki problem rozwiązuje właśnie guided_randomness:
require 'guided_randomness'
[1,2,3].get_rand [0.6, 0.3, 0.1]
to zwróci nam 1 z prawdopodobieństwem 60 procent, 2 z prawdopodobieństwem 30 procent, a 3 z prawdopodobieństwem 10 procent
ew. można też:
[1,2,3].get_rand [20, 30, 10]
wtedy zostanie to potraktowane jako liczba żądanych wystąpień, na sumę elementów podanej tabeli jako argument
(czyli np. 1 zostanie zwrócona z prawdopodobieństwem 20/(20+30+10) czyli 1/3)
Z ciekawości zajrzałem i jestem mocno zawiedziony. Dawno nie widziałem takiej proporcji ilości kodu do liczby niedoróbek. Ponieważ na co dzień jestem tak samo krytyczny wobec cudzego jak i swojego kodu to powiem Ci co bym poprawił na Twoim miejscu:
Monkey-patching - jeśli nie ma konkretnej przesłanki to nie stosować. Lepiej cały algorytm umieścić w klasie, np. GuidedRandomness/WeightedRandom i jeśli końcowy użytkownik będzie chciał MP to sobie takowy dorobi (ew. można umieścić dodatkowy plik guided_randomness/array_monkey_patch.rb, który będzie ładowany ręcznie przez usera).
Nazwa get_rand - do kosza. Trzeba znaleźć jakąś adekwatną. Hm… weighted_random (jeśli przenosimy algorytm do wspomnianej klasy to np. GuidedRandomness.random, WeightedRandom.random etc.)?
Formatowanie, brak konsekwencji przy białych znakach - masakra. Raz jest “|x,y|”, potem “|x, y|” (spacja), raz otaczasz spacją operator +/-, innym razem nie, jeszcze innym tylko z jednej strony. Je osobiście zawsze każdy operator typu +/-/* etc otaczam po 1 spacji z każdej strony. Zawsze. Niezależnie od preferencji konsekwentność to podstawa.
Zakomentowany kod. Rozumiem, że “na potem, może się jeszcze przyda”?
Nazwy zmiennych: hash, value (nieużywana), counter, arg, k, v (czyli prawie wszystkie) - do poprawy.
Algorytm. Osobiście wyznaję zasadę, że najpierw niech coś działa, a potem dobrze. Twój działa, chyba dobrze, ale wolno (o czym pewnie zdajesz sobie sprawę). Niezależnie od jego złożoności czasowej mam ogromne podejrzenia co do magicznej zmiennej counter oraz niezrozumiałej dla mnie magii z rand(3) - 1. Być może za tym kryje się jakaś sprytna sztuczka, ja jej nie widzę.
Lepszy i wydajniejszy algorytm polega na:
zsumowaniu wszystkich wag elementów
wylosowaniu liczby z przedziału [0, suma)
znalezieniu pierwszej liczby, dla której wylosowana liczba jest <= niż suma wag wszystkich wcześniejszych + jej
Wiem, że to nie wina Twojej biblioteki, ale nie działa z railsami, które dodają do klasy Array metodę rand co powoduje wywalenie się przy rand(3). Moja sugestia - wszystkie wywołania metody rand(x) zamienić na Kernel.rand(x).
Moja wersja (zostawiłem w klasie Array gdyż nie chciałem ruszać testów):
[code=ruby]class Array
def get_rand(weights)
raise “Wrong number of array elements!” unless weights.size == self.size
total = weights.inject(0, :+)
point = Kernel.rand * total
self.each_with_index do |element, i|
return element if weights[i] >= point
point -= weights[i]
end
end[/code]
Przed:
[code=shell]Loaded suite test/test
Started
…
Finished in 101.431378 seconds.
4 tests, 815 assertions, 0 failures, 0 errors
real 1m42.128s
user 1m34.898s
sys 0m2.431s[/code]
Po:
[code=shell]Loaded suite test/test
Started
…
Finished in 9.32682 seconds.
Niestety mój refactoring polegający na skorzystaniu z gema bsearch dodatkowo zaśmieca klasę Array. Za to w zamian dorzuciłem nowe ficzery takie jak samplowanie wiele razy tej samej tablicy za jednym zamachem oraz użycie binarnego wyszukiwania w celu znalezienia mapowania z losowej liczby na index wylosowanego elementu. Czas wykonania testów na moim komputerze wacha się około 4.7 s.
Oj tam oj tam, chyba trochę przesadzasz Oczywiście można by to zapisać w jakimś eachu i w kilku linijkach, ale też bez przesady, to jest zwykły inject, który wrzuca do nowej tablicy sumę danego elementu i elementów poprzednich.
Sam implementacja jest moim zdaniem zrozumiała natomiast nie przeszkodziłoby by gdyby została wyciągnieta do osobnej metody o wymownym tytule stanowiącym komentarz jej działania.