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