GitHub Viewer
"""Provides some utilities widely used by other modules"""
import bisect
import collections
import collections.abc
import functools
import heapq
import os.path
import random
from itertools import chain, combinations
from statistics import mean
import numpy as np
# part1. General data structures and their functions
# ______________________________________________________________________________
# Queues: Stack, FIFOQueue, PriorityQueue
# Stack and FIFOQueue are implemented as list and collection.deque
# PriorityQueue is implemented here
class PriorityQueue:
"""A Queue in which the minimum (or maximum) element (as determined by f and order) is returned first.
If order is 'min', the item with minimum f(x) is
returned first; if order is 'max', then it is the item with maximum f(x).
Also supports dict-like lookup."""
def __init__(self, order='min', f=lambda x: x):
self.heap = []
if order == 'min':
self.f = f
elif order == 'max': # now item with max f(x)
self.f = lambda x: -f(x) # will be popped first
else:
raise ValueError("Order must be either 'min' or 'max'.")
def append(self, item):
"""Insert item at its correct position."""
heapq.heappush(self.heap, (self.f(item), item))
def extend(self, items):
"""Insert each item in items at its correct position."""
for item in items:
self.append(item)
def pop(self):
"""Pop and return the item (with min or max f(x) value)
depending on the order."""
if self.heap:
return heapq.heappop(self.heap)[1]
else:
raise Exception('Trying to pop from empty PriorityQueue.')
def __len__(self):
"""Return current capacity of PriorityQueue."""
return len(self.heap)
def __contains__(self, key):
"""Return True if the key is in PriorityQueue."""
return any([item == key for _, item in self.heap])
def __getitem__(self, key):
"""Returns the first value associated with key in PriorityQueue.
Raises KeyError if key is not present."""
for value, item in self.heap:
if item == key:
return value
raise KeyError(str(key) + " is not in the priority queue")
def __delitem__(self, key):
"""Delete the first occurrence of key."""
try:
del self.heap[[item == key for _, item in self.heap].index(True)]
except ValueError:
raise KeyError(str(key) + " is not in the priority queue")
heapq.heapify(self.heap)
# ______________________________________________________________________________
# Functions on Sequences and Iterables
def sequence(iterable):
"""Converts iterable to sequence, if it is not already one."""
return (iterable if isinstance(iterable, collections.abc.Sequence)
else tuple([iterable]))
def remove_all(item, seq):
"""Return a copy of seq (or string) with all occurrences of item removed."""
if isinstance(seq, str):
return seq.replace(item, '')
elif isinstance(seq, set):
rest = seq.copy()
rest.remove(item)
return rest
else:
return [x for x in seq if x != item]
def unique(seq):
"""Remove duplicate elements from seq. Assumes hashable elements."""
return list(set(seq))
def count(seq):
"""Count the number of items in sequence that are interpreted as true."""
return sum(map(bool, seq))
def multimap(items):
"""Given (key, val) pairs, return {key: [val, ....], ...}."""
result = collections.defaultdict(list)
for (key, val) in items:
result[key].append(val)
return dict(result)
def multimap_items(mmap):
"""Yield all (key, val) pairs stored in the multimap."""
for (key, vals) in mmap.items():
for val in vals:
yield key, val
def product(numbers):
"""Return the product of the numbers, e.g. product([2, 3, 10]) == 60"""
result = 1
for x in numbers:
result *= x
return result
def first(iterable, default=None):
"""Return the first element of an iterable; or default."""
return next(iter(iterable), default)
def is_in(elt, seq):
"""Similar to (elt in seq), but compares with 'is', not '=='."""
return any(x is elt for x in seq)
def mode(data):
"""Return the most common data item. If there are ties, return any one of them."""
[(item, count)] = collections.Counter(data).most_common(1)
return item
def power_set(iterable):
"""power_set([1,2,3]) --> (1,) (2,) (3,) (1,2) (1,3) (2,3) (1,2,3)"""
s = list(iterable)
return list(chain.from_iterable(combinations(s, r) for r in range(len(s) + 1)))[1:]
def extend(s, var, val):
"""Copy dict s and extend it by setting var to val; return copy."""
return {**s, var: val}
def flatten(seqs):
return sum(seqs, [])
# ______________________________________________________________________________
# argmin and argmax
identity = lambda x: x
def argmin_random_tie(seq, key=identity):
"""Return a minimum element of seq; break ties at random."""
return min(shuffled(seq), key=key)
def argmax_random_tie(seq, key=identity):
"""Return an element with highest fn(seq[i]) score; break ties at random."""
return max(shuffled(seq), key=key)
def shuffled(iterable):
"""Randomly shuffle a copy of iterable."""
items = list(iterable)
random.shuffle(items)
return items
# part2. Mathematical and Statistical util functions
# ______________________________________________________________________________
def histogram(values, mode=0, bin_function=None):
"""Return a list of (value, count) pairs, summarizing the input values.
Sorted by increasing value, or if mode=1, by decreasing count.
If bin_function is given, map it over values first."""
if bin_function:
values = map(bin_function, values)
bins = {}
for val in values:
bins[val] = bins.get(val, 0) + 1
if mode:
return sorted(list(bins.items()), key=lambda x: (x[1], x[0]), reverse=True)
else:
return sorted(bins.items())
def element_wise_product(x, y):
if hasattr(x, '__iter__') and hasattr(y, '__iter__'):
assert len(x) == len(y)
return [element_wise_product(_x, _y) for _x, _y in zip(x, y)]
elif hasattr(x, '__iter__') == hasattr(y, '__iter__'):
return x * y
else:
raise Exception('Inputs must be in the same size!')
def vector_add(a, b):
"""Component-wise addition of two vectors."""
if not (a and b):
return a or b
if hasattr(a, '__iter__') and hasattr(b, '__iter__'):
assert len(a) == len(b)
return list(map(vector_add, a, b))
else:
try:
return a + b
except TypeError:
raise Exception('Inputs must be in the same size!')
def scalar_vector_product(x, y):
"""Return vector as a product of a scalar and a vector recursively."""
return [scalar_vector_product(x, _y) for _y in y] if hasattr(y, '__iter__') else x * y
def map_vector(f, x):
"""Apply function f to iterable x."""
return [map_vector(f, _x) for _x in x] if hasattr(x, '__iter__') else list(map(f, [x]))[0]
def probability(p):
"""Return true with probability p."""
return p > random.uniform(0.0, 1.0)
def weighted_sample_with_replacement(n, seq, weights):
"""Pick n samples from seq at random, with replacement, with the
probability of each element in proportion to its corresponding
weight."""
sample = weighted_sampler(seq, weights)
return [sample() for _ in range(n)]
def weighted_sampler(seq, weights):
"""Return a random-sample function that picks from seq weighted by weights."""
totals = []
for w in weights:
totals.append(w + totals[-1] if totals else w)
return lambda: seq[bisect.bisect(totals, random.uniform(0, totals[-1]))]
def weighted_choice(choices):
"""A weighted version of random.choice"""
# NOTE: Should be replaced by random.choices if we port to Python 3.6
total = sum(w for _, w in choices)
r = random.uniform(0, total)
upto = 0
for c, w in choices:
if upto + w >= r:
return c, w
upto += w
def rounder(numbers, d=4):
"""Round a single number, or sequence of numbers, to d decimal places."""
if isinstance(numbers, (int, float)):
return round(numbers, d)
else:
constructor = type(numbers) # Can be list, set, tuple, etc.
return constructor(rounder(n, d) for n in numbers)
def num_or_str(x): # TODO: rename as `atom`
"""The argument is a string; convert to a number if
possible, or strip it."""
try:
return int(x)
except ValueError:
try:
return float(x)
except ValueError:
return str(x).strip()
def euclidean_distance(x, y):
return np.sqrt(sum((_x - _y) ** 2 for _x, _y in zip(x, y)))
def manhattan_distance(x, y):
return sum(abs(_x - _y) for _x, _y in zip(x, y))
def hamming_distance(x, y):
return sum(_x != _y for _x, _y in zip(x, y))
def rms_error(x, y):
return np.sqrt(ms_error(x, y))
def ms_error(x, y):
return mean((x - y) ** 2 for x, y in zip(x, y))
def mean_error(x, y):
return mean(abs(x - y) for x, y in zip(x, y))
def mean_boolean_error(x, y):
return mean(_x != _y for _x, _y in zip(x, y))
# part3. Neural network util functions
# ______________________________________________________________________________
def cross_entropy_loss(x, y):
"""Cross entropy loss function. x and y are 1D iterable objects."""
return (-1.0 / len(x)) * sum(x * np.log(_y) + (1 - _x) * np.log(1 - _y) for _x, _y in zip(x, y))
def mean_squared_error_loss(x, y):
"""Min square loss function. x and y are 1D iterable objects."""
return (1.0 / len(x)) * sum((_x - _y) ** 2 for _x, _y in zip(x, y))
def normalize(dist):
"""Multiply each number by a constant such that the sum is 1.0"""
if isinstance(dist, dict):
total = sum(dist.values())
for key in dist:
dist[key] = dist[key] / total
assert 0