Skip to content

Instantly share code, notes, and snippets.

@nbness2
Last active September 12, 2018 22:10
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save nbness2/c064570a091b5ff817dc35e082ad1ea4 to your computer and use it in GitHub Desktop.
Save nbness2/c064570a091b5ff817dc35e082ad1ea4 to your computer and use it in GitHub Desktop.
Python RandomUtil
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)
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")
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")]
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)
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