157 lines
4.5 KiB
Python
157 lines
4.5 KiB
Python
|
"""Various utility functions."""
|
||
|
from collections import namedtuple, OrderedDict
|
||
|
|
||
|
|
||
|
__unittest = True
|
||
|
|
||
|
_MAX_LENGTH = 80
|
||
|
def safe_repr(obj, short=False):
|
||
|
try:
|
||
|
result = repr(obj)
|
||
|
except Exception:
|
||
|
result = object.__repr__(obj)
|
||
|
if not short or len(result) < _MAX_LENGTH:
|
||
|
return result
|
||
|
return result[:_MAX_LENGTH] + ' [truncated]...'
|
||
|
|
||
|
|
||
|
def strclass(cls):
|
||
|
return "%s.%s" % (cls.__module__, cls.__name__)
|
||
|
|
||
|
def sorted_list_difference(expected, actual):
|
||
|
"""Finds elements in only one or the other of two, sorted input lists.
|
||
|
|
||
|
Returns a two-element tuple of lists. The first list contains those
|
||
|
elements in the "expected" list but not in the "actual" list, and the
|
||
|
second contains those elements in the "actual" list but not in the
|
||
|
"expected" list. Duplicate elements in either input list are ignored.
|
||
|
"""
|
||
|
i = j = 0
|
||
|
missing = []
|
||
|
unexpected = []
|
||
|
while True:
|
||
|
try:
|
||
|
e = expected[i]
|
||
|
a = actual[j]
|
||
|
if e < a:
|
||
|
missing.append(e)
|
||
|
i += 1
|
||
|
while expected[i] == e:
|
||
|
i += 1
|
||
|
elif e > a:
|
||
|
unexpected.append(a)
|
||
|
j += 1
|
||
|
while actual[j] == a:
|
||
|
j += 1
|
||
|
else:
|
||
|
i += 1
|
||
|
try:
|
||
|
while expected[i] == e:
|
||
|
i += 1
|
||
|
finally:
|
||
|
j += 1
|
||
|
while actual[j] == a:
|
||
|
j += 1
|
||
|
except IndexError:
|
||
|
missing.extend(expected[i:])
|
||
|
unexpected.extend(actual[j:])
|
||
|
break
|
||
|
return missing, unexpected
|
||
|
|
||
|
|
||
|
def unorderable_list_difference(expected, actual, ignore_duplicate=False):
|
||
|
"""Same behavior as sorted_list_difference but
|
||
|
for lists of unorderable items (like dicts).
|
||
|
|
||
|
As it does a linear search per item (remove) it
|
||
|
has O(n*n) performance.
|
||
|
"""
|
||
|
missing = []
|
||
|
unexpected = []
|
||
|
while expected:
|
||
|
item = expected.pop()
|
||
|
try:
|
||
|
actual.remove(item)
|
||
|
except ValueError:
|
||
|
missing.append(item)
|
||
|
if ignore_duplicate:
|
||
|
for lst in expected, actual:
|
||
|
try:
|
||
|
while True:
|
||
|
lst.remove(item)
|
||
|
except ValueError:
|
||
|
pass
|
||
|
if ignore_duplicate:
|
||
|
while actual:
|
||
|
item = actual.pop()
|
||
|
unexpected.append(item)
|
||
|
try:
|
||
|
while True:
|
||
|
actual.remove(item)
|
||
|
except ValueError:
|
||
|
pass
|
||
|
return missing, unexpected
|
||
|
|
||
|
# anything left in actual is unexpected
|
||
|
return missing, actual
|
||
|
|
||
|
_Mismatch = namedtuple('Mismatch', 'actual expected value')
|
||
|
|
||
|
def _count_diff_all_purpose(actual, expected):
|
||
|
'Returns list of (cnt_act, cnt_exp, elem) triples where the counts differ'
|
||
|
# elements need not be hashable
|
||
|
s, t = list(actual), list(expected)
|
||
|
m, n = len(s), len(t)
|
||
|
NULL = object()
|
||
|
result = []
|
||
|
for i, elem in enumerate(s):
|
||
|
if elem is NULL:
|
||
|
continue
|
||
|
cnt_s = cnt_t = 0
|
||
|
for j in range(i, m):
|
||
|
if s[j] == elem:
|
||
|
cnt_s += 1
|
||
|
s[j] = NULL
|
||
|
for j, other_elem in enumerate(t):
|
||
|
if other_elem == elem:
|
||
|
cnt_t += 1
|
||
|
t[j] = NULL
|
||
|
if cnt_s != cnt_t:
|
||
|
diff = _Mismatch(cnt_s, cnt_t, elem)
|
||
|
result.append(diff)
|
||
|
|
||
|
for i, elem in enumerate(t):
|
||
|
if elem is NULL:
|
||
|
continue
|
||
|
cnt_t = 0
|
||
|
for j in range(i, n):
|
||
|
if t[j] == elem:
|
||
|
cnt_t += 1
|
||
|
t[j] = NULL
|
||
|
diff = _Mismatch(0, cnt_t, elem)
|
||
|
result.append(diff)
|
||
|
return result
|
||
|
|
||
|
def _ordered_count(iterable):
|
||
|
'Return dict of element counts, in the order they were first seen'
|
||
|
c = OrderedDict()
|
||
|
for elem in iterable:
|
||
|
c[elem] = c.get(elem, 0) + 1
|
||
|
return c
|
||
|
|
||
|
def _count_diff_hashable(actual, expected):
|
||
|
'Returns list of (cnt_act, cnt_exp, elem) triples where the counts differ'
|
||
|
# elements must be hashable
|
||
|
s, t = _ordered_count(actual), _ordered_count(expected)
|
||
|
result = []
|
||
|
for elem, cnt_s in s.items():
|
||
|
cnt_t = t.get(elem, 0)
|
||
|
if cnt_s != cnt_t:
|
||
|
diff = _Mismatch(cnt_s, cnt_t, elem)
|
||
|
result.append(diff)
|
||
|
for elem, cnt_t in t.items():
|
||
|
if elem not in s:
|
||
|
diff = _Mismatch(0, cnt_t, elem)
|
||
|
result.append(diff)
|
||
|
return result
|