Used isort to sort imports. Also added to the git pre-commit hook.
[python_utils.git] / math_utils.py
index 56fb7072366ab97621e032e9aed11d13d7740b5e..3953ae585d249123c17e82f4a829ad68cf442c0b 100644 (file)
@@ -2,11 +2,25 @@
 
 import functools
 import math
+from heapq import heappop, heappush
 from typing import List
-from heapq import heappush, heappop
 
 
-class RunningMedian:
+class RunningMedian(object):
+    """A running median computer.
+
+    >>> median = RunningMedian()
+    >>> median.add_number(1)
+    >>> median.add_number(10)
+    >>> median.add_number(3)
+    >>> median.get_median()
+    3
+    >>> median.add_number(7)
+    >>> median.add_number(5)
+    >>> median.get_median()
+    5
+    """
+
     def __init__(self):
         self.lowers, self.highers = [], []
 
@@ -25,7 +39,7 @@ class RunningMedian:
 
     def get_median(self):
         if len(self.lowers) == len(self.highers):
-            return (-self.lowers[0] + self.highers[0])/2
+            return (-self.lowers[0] + self.highers[0]) / 2
         elif len(self.lowers) > len(self.highers):
             return -self.lowers[0]
         else:
@@ -55,15 +69,69 @@ def gcd_float_sequence(lst: List[float]) -> float:
 
 
 def truncate_float(n: float, decimals: int = 2):
-    """Truncate a float to a particular number of decimals."""
+    """
+    Truncate a float to a particular number of decimals.
+
+    >>> truncate_float(3.1415927, 3)
+    3.141
+
+    """
     assert decimals > 0 and decimals < 10
     multiplier = 10 ** decimals
     return int(n * multiplier) / multiplier
 
 
+def percentage_to_multiplier(percent: float) -> float:
+    """Given a percentage (e.g. 155%), return a factor needed to scale a
+    number by that percentage.
+
+    >>> percentage_to_multiplier(155)
+    2.55
+    >>> percentage_to_multiplier(45)
+    1.45
+    >>> percentage_to_multiplier(-25)
+    0.75
+
+    """
+    multiplier = percent / 100
+    multiplier += 1.0
+    return multiplier
+
+
+def multiplier_to_percent(multiplier: float) -> float:
+    """Convert a multiplicative factor into a percent change.
+
+    >>> multiplier_to_percent(0.75)
+    -25.0
+    >>> multiplier_to_percent(1.0)
+    0.0
+    >>> multiplier_to_percent(1.99)
+    99.0
+
+    """
+    percent = multiplier
+    if percent > 0.0:
+        percent -= 1.0
+    else:
+        percent = 1.0 - percent
+    percent *= 100.0
+    return percent
+
+
 @functools.lru_cache(maxsize=1024, typed=True)
 def is_prime(n: int) -> bool:
-    """Returns True if n is prime and False otherwise"""
+    """
+    Returns True if n is prime and False otherwise.  Obviously(?) very slow for
+    very large input numbers.
+
+    >>> is_prime(13)
+    True
+    >>> is_prime(22)
+    False
+    >>> is_prime(51602981)
+    True
+
+    """
     if not isinstance(n, int):
         raise TypeError("argument passed to is_prime is not of 'int' type")
 
@@ -75,12 +143,18 @@ def is_prime(n: int) -> bool:
 
     # This is checked so that we can skip middle five numbers in below
     # loop
-    if (n % 2 == 0 or n % 3 == 0):
+    if n % 2 == 0 or n % 3 == 0:
         return False
 
     i = 5
     while i * i <= n:
-        if (n % i == 0 or n % (i + 2) == 0):
+        if n % i == 0 or n % (i + 2) == 0:
             return False
         i = i + 6
     return True
+
+
+if __name__ == '__main__':
+    import doctest
+
+    doctest.testmod()