Last active
September 12, 2018 22:10
-
-
Save nbness2/c064570a091b5ff817dc35e082ad1ea4 to your computer and use it in GitHub Desktop.
Python RandomUtil
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import random | |
class BaseRandom: # Float\double takes way way too long lol just use random.random() [0, 1] | |
def __init__(self, rand: random.Random=None): | |
self.__next_by_type = { | |
"b": self.__nextbyte, | |
"s": self.__nextshort, | |
"i": self.__nextint, | |
"l": self.__nextlong, | |
"a": self.__nextany | |
} | |
self.__random = rand if rand is not None and type(rand) is random.Random else random.Random() | |
def __next_arbitrary(self, max_value: int, rand: random.Random, lower_bound: int, upper_bound: int, inclusive: bool) -> int: | |
lower_bound_ = min(lower_bound, upper_bound) | |
upper_bound_ = max(lower_bound, upper_bound) | |
if type(max_value) in (int, float): | |
lower_bound_ %= max_value | |
upper_bound_ %= max_value | |
if rand is None: | |
rand = self.__random | |
return rand.randint(lower_bound_, upper_bound_ - int(not inclusive)) | |
def __nextbyte(self, lower_bound: int, upper_bound: int, inclusive: bool=True, rand: random.Random=None): | |
return self.__next_arbitrary(0xFF, rand, lower_bound, upper_bound, inclusive) | |
def __nextshort(self, lower_bound: int, upper_bound: int, inclusive: bool=True, rand: random.Random=None): | |
return self.__next_arbitrary(0xFFFF, rand, lower_bound, upper_bound, inclusive) | |
def __nextint(self, lower_bound: int, upper_bound: int, inclusive: bool=True, rand: random.Random=None): | |
return self.__next_arbitrary(0xFFFFFFFF, rand, lower_bound, upper_bound, inclusive) | |
def __nextlong(self, lower_bound: int, upper_bound: int, inclusive: bool=True, rand: random.Random=None): | |
return self.__next_arbitrary(0xFFFFFFFFFFFFFFFF, rand, lower_bound, upper_bound, inclusive) | |
def __nextany(self, lower_bound: int, upper_bound: int, inclusive: bool, rand: random.Random=None): | |
return self.__next_arbitrary(None, rand, lower_bound, upper_bound, inclusive) | |
def random(self, lower_bound: int, upper_bound: int, inclusive: bool=True, rand_type: str="a"): | |
rand_type = str(rand_type)[0].lower() | |
return self.__next_by_type[rand_type](lower_bound, upper_bound, inclusive, self.__random) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import BaseRandom | |
class RandomRange(BaseRandom.BaseRandom): | |
def __init__(self, minimum: int, maximum: int, inclusive: bool): | |
super(self.__class__, self).__init__(None) | |
self.minimum = min(minimum, maximum) | |
self.maximum = max(minimum, maximum) | |
self.inclusive = bool(inclusive) | |
def pick(self, inclusive: bool=None): | |
return self.random(self.minimum, self.maximum, self.inclusive if inclusive is None or type(inclusive) is not bool else inclusive, "a") |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import BaseRandom | |
class RandomTable(BaseRandom.BaseRandom): | |
def __init__(self, items): | |
BaseRandom.BaseRandom.__init__(self, None) | |
self.__items = tuple(items) | |
self.__results_map = {} | |
self.__results_list = [] | |
def __contains__(self, item): | |
return item in self.__items | |
def __getitem__(self, item) -> int: | |
if type(item) is int: | |
return self.__items[item] | |
def index_of(self, item): | |
if item not in self: | |
raise ValueError(f"{item} not contained") | |
return self.__items.index(item) | |
def chance_of(self, item): | |
self.index_of(item) | |
return 100 / len(self.__items) | |
def items(self): | |
return self.__items[::] | |
def sort_items_by(self, other: list) -> None: | |
if len(other) != len(self.__items): | |
raise ValueError(f"Length of other ({len(other)}) must be same as length of items ({len(self.__items)})") | |
self.__items = tuple(zip(*sorted(zip(other, self.__items))))[1] | |
def pick_map(self, pick_amount, modifier=0): | |
return self.pick(pick_amount=pick_amount, modifier=modifier, ordered=False) | |
def pick_ordered(self, pick_amount, modifier=0): | |
return self.pick(pick_amount=pick_amount, modifier=modifier, ordered=True) | |
def pick(self, pick_amount=1, modifier=0, ordered=False): | |
if pick_amount < 1: | |
raise ValueError( | |
f"Cannot pick a negative ({pick_amount}) amount of items from {self.__class__.__qualname__}" | |
) | |
if pick_amount == 1: | |
return self._pick_internal(modifier=modifier) | |
if pick_amount > 1: | |
if ordered: | |
self.__results_list.clear() | |
for run in range(pick_amount): | |
self.__results_list.append(self._pick_internal(modifier=modifier)) | |
return self.__results_list.copy() | |
else: | |
self.__results_map.clear() | |
for run in range(pick_amount): | |
result = self._pick_internal(modifier=modifier) | |
if result not in self.__results_map: | |
self.__results_map[result] = 0 | |
self.__results_map[result] += 1 | |
return self.__results_map.copy() | |
# OVERRIDE THIS FUNCTION, not pick, pick_ordered, pick_map | |
def _pick_internal(self, *args, **kwargs): | |
return self.__items[self.random(0, len(self.__items), False, "i")] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
from BaseRandom import BaseRandom | |
from RandomRange import RandomRange | |
from RandomTable import RandomTable | |
from WeightedTable import WeightedTable | |
def BRTest(min_value, max_value, inclusive): | |
br = BaseRandom() | |
counter = 0 | |
is_min = False | |
is_max = False | |
inclusive_max = max_value - int(not inclusive) | |
print(f"Starting BaseRandom test from {min_value} to {max_value} {'' if inclusive else 'not '}inclusive") | |
while not is_min or not is_max: | |
result = br.random(min_value, max_value, inclusive) | |
counter += 1 | |
if not is_min and result == min_value: | |
print(f"BaseRandom min ({min_value}) picked at {counter} picks") | |
is_min = True | |
elif not is_max and result == inclusive_max: | |
print(f"BaseRandom max ({max_value}) picked at {counter} picks") | |
is_max = True | |
elif result > inclusive_max or result < min_value: | |
raise ValueError(f"BaseRandom error {result} out of range") | |
print(f"BaseRandom finished at {counter} picks") | |
print() | |
def RRTest(min_value, max_value, inclusive): | |
rr = RandomRange(min_value, max_value, inclusive) | |
counter = 0 | |
is_min = False | |
is_max = False | |
inclusive_max = max_value - int(not inclusive) | |
print(f"Starting RandomTable test from {min_value} to {max_value} {'' if inclusive else 'not '}inclusive") | |
while not is_min or not is_max: | |
result = rr.pick() | |
counter += 1 | |
if not is_min and result == min_value: | |
print(f"RandomTable min ({min_value}) picked at {counter} picks") | |
is_min = True | |
elif not is_max and result == inclusive_max: | |
print(f"RandomTable max ({max_value}) picked at {counter} picks") | |
is_max = True | |
elif result > inclusive_max or result < min_value: | |
raise ValueError(f"BaseRandom error {result} out of range") | |
print(f"RandomTable finished at {counter} picks") | |
print() | |
def RTTest(items, pick_amount): | |
rt = RandomTable(items) | |
print(f"Starting RandomTable test with items:\n\t{items}") | |
print(f"Each item has a {100 / len(items)}% chance to be picked") | |
print(f"RandomTable finished, results over {pick_amount} picks:\n\t{rt.pick(pick_amount)}") | |
print() | |
def WTTest(items, weights, pick_amount, modifier=0): | |
wt = WeightedTable(items, weights) | |
print(f"Starting WeightedTable test with items:\n\t{items}\nand weights:\n\t{weights}\ntotal weight: {sum(weights)}") | |
expected = {item: wt.chance_of(item) for item in items} | |
print(f"Expected chances:\n\t{expected}") | |
print(f"Actual results over {pick_amount} picks:\n\t{wt.pick(pick_amount=pick_amount, modifier=modifier)}") | |
if __name__ == "__main__": | |
min_value = -250_000 | |
max_value = 250_000 | |
inclusive = False | |
pick_amount = 100_000 | |
items = ("String1", "String2", "String3", "String4", "String5", "String6", "String7", "String8") | |
weights = (5, 6, 1, 4, 25, 8, 10, 2) | |
WTTest(items, weights, pick_amount) | |
BRTest(min_value, max_value, False) | |
RRTest(min_value, max_value, False) | |
RTTest(items, pick_amount) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import RandomTable | |
import RandomRange | |
class WeightedTable(RandomTable.RandomTable): | |
def __init__(self, items, weights): | |
super(self.__class__, self).__init__(items[::]) | |
self.sort_items_by(weights) | |
for weight in weights: | |
if weights.count(weight) > 1: | |
raise ValueError(f"Cannot have more than 1 item per weight in {self.__class__.__qualname__}") | |
elif weight < 1: | |
raise ValueError(f"Weight cannot be less than 1 in {self.__class__.__qualname__}") | |
self.__weights = sorted(weights[::]) | |
self.__total_weight = sum(self.__weights) | |
self.__weight_range = RandomRange.RandomRange(1, self.__total_weight, True) | |
def chance_of(self, item): | |
return self.__weights[self.index_of(item)] / self.__total_weight | |
def _pick_internal(self, modifier): | |
picked_weight = self.__weight_range.pick() * (1 / (1 + (modifier / 100))) | |
for (weight_idx, weight_value) in enumerate(self.__weights): | |
picked_weight -= weight_value | |
if picked_weight <= 0: | |
return self[weight_idx] | |
return self[-1] |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment