Most pythonic way of rotating a circular list to a canonical point
Tim Chase
python.list at tim.thechases.com
Sun Aug 2 14:19:16 EDT 2015
On 2015-08-01 13:34, Lukas Barth wrote:
> I have a list of numbers that I treat as "circular", i.e. [1,2,3]
> and [2,3,1] should be the same. Now I want to rotate these to a
> well defined status, so that I can can compare them.
>
> If all elements are unique, the solution is easy: find the minimum
> element, find its index, then use mylist[:index] + mylist[index:],
> i.e. the minimum element will always be at the beginning.
>
> But say I have [0,1,0,2,0,3]. I can in fact guarantee that no
> *pair* will appear twice in that list, i.e. I could search for the
> minimum, if that is unique go on as above, otherwise find *all*
> positions of the minimum, see which is followed by the smallest
> element, and then rotate that position to the front.
Well, you can pull the minimum (or maximum) of all the rotations to
get the "canonical" version:
def canonical(lst):
"""return the "canonical" representation of a list"""
return min(
lst if i == 0 else (lst[i:] + lst[:i])
for i in range(len(lst))
)
which you can determine, then hash once, and store. It's not a cheap
operation, but once you've determined the canonical/hash version,
then equality-testing becomes an O(1) test if comparing hashes, or
O(N) if comparing lists rather than an O(N*2) brute-force test
(which could be lower depending on the commonality).
def circular_compare1(a, b):
if a == b: return True
return any(a == (b[i:] + b[:i]) for i in range(1, len(b)))
def circular_compare2(a, b):
lena = len(a)
if lena != len(b): return False
return any(
all(a[i] == b[(i + offset) % lena] for i in range(lena))
for offset in range(lena)
)
for fn in (circular_compare1, circular_compare2):
for (a, b), expected in (
(([1,2,3], [1,2,3]), True), # the same
(([1,2,3], [3,1,2]), True), # simple rotation
(([1,2,3], [1,2,3,4]), False), # mismatched lengths
(([0,1,0,2,0,3], [0,2,0,3,0,1]), True), # repeated elements, simple rotation
):
result = bool(fn(a, b))
if result == expected:
print "PASS %s %r/%r=%s, not %s" % (
fn.__name__, a, b, result, expected
)
else:
print "FAIL %s %r/%r=%s, not %s" % (
fn.__name__, a, b, result, expected
)
ca = canonical(a)
cb = canonical(b)
print " Canonical A:", ca
print " Canonical B:", cb
print " Equal:", (ca == cb)
-tim
More information about the Python-list
mailing list