#!/usr/bin/python3 -i # # Copyright (c) 2019 Collabora, Ltd. # # SPDX-License-Identifier: Apache-2.0 # # Author(s): Ryan Pavlik """RecursiveMemoize serves as a base class for a function modeled as a dictionary computed on-the-fly.""" class RecursiveMemoize: """Base class for functions that are recursive. Derive and implement `def compute(self, key):` to perform the computation: you may use __getitem__ (aka self[otherkey]) to access the results for another key. Each value will be computed at most once. Your function should never return None, since it is used as a sentinel here. """ def __init__(self, func, key_iterable=None, permit_cycles=False): """Initialize data structures, and optionally compute/cache the answer for all elements of an iterable. If permit_cycles is False, then __getitem__ on something that's currently being computed raises an exception. If permit_cycles is True, then __getitem__ on something that's currently being computed returns None. """ self._compute = func self.permit_cycles = permit_cycles self.d = {} if key_iterable: # If we were given an iterable, let's populate those. for key in key_iterable: _ = self[key] def __getitem__(self, key): """Access the result of computing the function on the input. Performed lazily and cached. Implement `def compute(self, key):` with the actual function, which will be called on demand.""" if key in self.d: ret = self.d[key] # Detect "we're computing this" sentinel and # fail if cycles not permitted if ret is None and not self.permit_cycles: raise RuntimeError("Cycle detected when computing function: " + "f({}) depends on itself".format(key)) # return the memoized value # (which might be None if we're in a cycle that's permitted) return ret # Set sentinel for "we're computing this" self.d[key] = None # Delegate to function to actually compute ret = self._compute(key) # Memoize self.d[key] = ret return ret def get_dict(self): """Return the dictionary where memoized results are stored. DO NOT MODIFY!""" return self.d