1e5c31af7Sopenharmony_ci#!/usr/bin/python3 -i 2e5c31af7Sopenharmony_ci# 3e5c31af7Sopenharmony_ci# Copyright (c) 2019 Collabora, Ltd. 4e5c31af7Sopenharmony_ci# 5e5c31af7Sopenharmony_ci# SPDX-License-Identifier: Apache-2.0 6e5c31af7Sopenharmony_ci# 7e5c31af7Sopenharmony_ci# Author(s): Ryan Pavlik <ryan.pavlik@collabora.com> 8e5c31af7Sopenharmony_ci"""RecursiveMemoize serves as a base class for a function modeled 9e5c31af7Sopenharmony_cias a dictionary computed on-the-fly.""" 10e5c31af7Sopenharmony_ci 11e5c31af7Sopenharmony_ci 12e5c31af7Sopenharmony_ciclass RecursiveMemoize: 13e5c31af7Sopenharmony_ci """Base class for functions that are recursive. 14e5c31af7Sopenharmony_ci 15e5c31af7Sopenharmony_ci Derive and implement `def compute(self, key):` to perform the computation: 16e5c31af7Sopenharmony_ci you may use __getitem__ (aka self[otherkey]) to access the results for 17e5c31af7Sopenharmony_ci another key. Each value will be computed at most once. Your 18e5c31af7Sopenharmony_ci function should never return None, since it is used as a sentinel here. 19e5c31af7Sopenharmony_ci 20e5c31af7Sopenharmony_ci """ 21e5c31af7Sopenharmony_ci 22e5c31af7Sopenharmony_ci def __init__(self, func, key_iterable=None, permit_cycles=False): 23e5c31af7Sopenharmony_ci """Initialize data structures, and optionally compute/cache the answer 24e5c31af7Sopenharmony_ci for all elements of an iterable. 25e5c31af7Sopenharmony_ci 26e5c31af7Sopenharmony_ci If permit_cycles is False, then __getitem__ on something that's 27e5c31af7Sopenharmony_ci currently being computed raises an exception. 28e5c31af7Sopenharmony_ci If permit_cycles is True, then __getitem__ on something that's 29e5c31af7Sopenharmony_ci currently being computed returns None. 30e5c31af7Sopenharmony_ci """ 31e5c31af7Sopenharmony_ci self._compute = func 32e5c31af7Sopenharmony_ci self.permit_cycles = permit_cycles 33e5c31af7Sopenharmony_ci self.d = {} 34e5c31af7Sopenharmony_ci if key_iterable: 35e5c31af7Sopenharmony_ci # If we were given an iterable, let's populate those. 36e5c31af7Sopenharmony_ci for key in key_iterable: 37e5c31af7Sopenharmony_ci _ = self[key] 38e5c31af7Sopenharmony_ci 39e5c31af7Sopenharmony_ci def __getitem__(self, key): 40e5c31af7Sopenharmony_ci """Access the result of computing the function on the input. 41e5c31af7Sopenharmony_ci 42e5c31af7Sopenharmony_ci Performed lazily and cached. 43e5c31af7Sopenharmony_ci Implement `def compute(self, key):` with the actual function, 44e5c31af7Sopenharmony_ci which will be called on demand.""" 45e5c31af7Sopenharmony_ci if key in self.d: 46e5c31af7Sopenharmony_ci ret = self.d[key] 47e5c31af7Sopenharmony_ci # Detect "we're computing this" sentinel and 48e5c31af7Sopenharmony_ci # fail if cycles not permitted 49e5c31af7Sopenharmony_ci if ret is None and not self.permit_cycles: 50e5c31af7Sopenharmony_ci raise RuntimeError("Cycle detected when computing function: " + 51e5c31af7Sopenharmony_ci "f({}) depends on itself".format(key)) 52e5c31af7Sopenharmony_ci # return the memoized value 53e5c31af7Sopenharmony_ci # (which might be None if we're in a cycle that's permitted) 54e5c31af7Sopenharmony_ci return ret 55e5c31af7Sopenharmony_ci 56e5c31af7Sopenharmony_ci # Set sentinel for "we're computing this" 57e5c31af7Sopenharmony_ci self.d[key] = None 58e5c31af7Sopenharmony_ci # Delegate to function to actually compute 59e5c31af7Sopenharmony_ci ret = self._compute(key) 60e5c31af7Sopenharmony_ci # Memoize 61e5c31af7Sopenharmony_ci self.d[key] = ret 62e5c31af7Sopenharmony_ci 63e5c31af7Sopenharmony_ci return ret 64e5c31af7Sopenharmony_ci 65e5c31af7Sopenharmony_ci def get_dict(self): 66e5c31af7Sopenharmony_ci """Return the dictionary where memoized results are stored. 67e5c31af7Sopenharmony_ci 68e5c31af7Sopenharmony_ci DO NOT MODIFY!""" 69e5c31af7Sopenharmony_ci return self.d 70e5c31af7Sopenharmony_ci 71e5c31af7Sopenharmony_ci 72e5c31af7Sopenharmony_cidef longest_common_prefix(strings): 73e5c31af7Sopenharmony_ci """ 74e5c31af7Sopenharmony_ci Find the longest common prefix of a list of 2 or more strings. 75e5c31af7Sopenharmony_ci 76e5c31af7Sopenharmony_ci Args: 77e5c31af7Sopenharmony_ci strings (collection): at least 2 strings. 78e5c31af7Sopenharmony_ci 79e5c31af7Sopenharmony_ci Returns: 80e5c31af7Sopenharmony_ci string: The longest string that all submitted strings start with. 81e5c31af7Sopenharmony_ci 82e5c31af7Sopenharmony_ci >>> longest_common_prefix(["abcd", "abce"]) 83e5c31af7Sopenharmony_ci 'abc' 84e5c31af7Sopenharmony_ci 85e5c31af7Sopenharmony_ci """ 86e5c31af7Sopenharmony_ci assert(len(strings) > 1) 87e5c31af7Sopenharmony_ci a = min(strings) 88e5c31af7Sopenharmony_ci b = max(strings) 89e5c31af7Sopenharmony_ci prefix = [] 90e5c31af7Sopenharmony_ci for a_char, b_char in zip(a, b): 91e5c31af7Sopenharmony_ci if a_char == b_char: 92e5c31af7Sopenharmony_ci prefix.append(a_char) 93e5c31af7Sopenharmony_ci else: 94e5c31af7Sopenharmony_ci break 95e5c31af7Sopenharmony_ci return "".join(prefix) 96e5c31af7Sopenharmony_ci 97e5c31af7Sopenharmony_ci 98e5c31af7Sopenharmony_cidef longest_common_token_prefix(strings, delimiter='_'): 99e5c31af7Sopenharmony_ci """ 100e5c31af7Sopenharmony_ci Find the longest common token-wise prefix of a list of 2 or more strings. 101e5c31af7Sopenharmony_ci 102e5c31af7Sopenharmony_ci Args: 103e5c31af7Sopenharmony_ci strings (collection): at least 2 strings. 104e5c31af7Sopenharmony_ci delimiter (character): the character to split on. 105e5c31af7Sopenharmony_ci 106e5c31af7Sopenharmony_ci Returns: 107e5c31af7Sopenharmony_ci string: The longest string that all submitted strings start with. 108e5c31af7Sopenharmony_ci 109e5c31af7Sopenharmony_ci >>> longest_common_token_prefix(["xr_abc_123", "xr_abc_567"]) 110e5c31af7Sopenharmony_ci 'xr_abc_' 111e5c31af7Sopenharmony_ci 112e5c31af7Sopenharmony_ci "1" is in the per-character longest common prefix, but 123 != 135, 113e5c31af7Sopenharmony_ci so it's not in the per-token prefix. 114e5c31af7Sopenharmony_ci 115e5c31af7Sopenharmony_ci >>> longest_common_token_prefix(["xr_abc_123", "xr_abc_135"]) 116e5c31af7Sopenharmony_ci 'xr_abc_' 117e5c31af7Sopenharmony_ci 118e5c31af7Sopenharmony_ci Here, the prefix is actually the entirety of one string, so no trailing delimiter. 119e5c31af7Sopenharmony_ci 120e5c31af7Sopenharmony_ci >>> longest_common_token_prefix(["xr_abc_123", "xr_abc"]) 121e5c31af7Sopenharmony_ci 'xr_abc' 122e5c31af7Sopenharmony_ci 123e5c31af7Sopenharmony_ci 124e5c31af7Sopenharmony_ci No common prefix here, because it's per-token: 125e5c31af7Sopenharmony_ci 126e5c31af7Sopenharmony_ci >>> longest_common_token_prefix(["abc_123", "ab_123"]) 127e5c31af7Sopenharmony_ci '' 128e5c31af7Sopenharmony_ci 129e5c31af7Sopenharmony_ci """ 130e5c31af7Sopenharmony_ci assert(len(strings) > 1) 131e5c31af7Sopenharmony_ci a = min(strings).split(delimiter) 132e5c31af7Sopenharmony_ci b = max(strings).split(delimiter) 133e5c31af7Sopenharmony_ci prefix_tokens = [] 134e5c31af7Sopenharmony_ci for a_token, b_token in zip(a, b): 135e5c31af7Sopenharmony_ci if a_token == b_token: 136e5c31af7Sopenharmony_ci prefix_tokens.append(a_token) 137e5c31af7Sopenharmony_ci else: 138e5c31af7Sopenharmony_ci break 139e5c31af7Sopenharmony_ci if prefix_tokens: 140e5c31af7Sopenharmony_ci prefix = delimiter.join(prefix_tokens) 141e5c31af7Sopenharmony_ci if len(prefix_tokens) < min(len(a), len(b)): 142e5c31af7Sopenharmony_ci # This is truly a prefix, not just one of the strings. 143e5c31af7Sopenharmony_ci prefix += delimiter 144e5c31af7Sopenharmony_ci return prefix 145e5c31af7Sopenharmony_ci return '' 146