Skip to content

How to Check if Two Lists are Circularly Identical in Python

Learn how to determine if one list is a rotated version of another in Python using efficient methods like concatenation, deque rotation, and optimized index checks.

Two lists are circularly identical if one is a rotated version of the other. For example:

  • List A: [10, 20, 30, 40]
  • List B: [30, 40, 10, 20] (A rotated by 2 positions)

This problem appears in geometry processing, pattern recognition, and cyclic sequence analysis.

The Concatenation Trick

The most elegant solution uses a key insight: If B is a rotation of A, then B must appear as a contiguous subsequence within A + A.

Visual proof:

A     = [1, 2, 3]
A + A = [1, 2, 3, 1, 2, 3]

All rotations are visible:
  [1, 2, 3] - original (position 0)
  [2, 3, 1] - rotation (position 1)  
  [3, 1, 2] - rotation (position 2)

Implementation

def is_circularly_identical(list_a: list, list_b: list) -> bool:
    """Check if list_b is a rotation of list_a."""
    if len(list_a) != len(list_b):
        return False

    if not list_a:
        return True

    doubled = list_a + list_a
    n = len(list_b)

    for i in range(len(list_a)):
        if doubled[i:i + n] == list_b:
            return True

    return False


a = [1, 2, 3, 4]
b = [3, 4, 1, 2]
c = [2, 1, 3, 4]

print(is_circularly_identical(a, b))  # True (rotated by 2)
print(is_circularly_identical(a, c))  # False (not a rotation)

Using collections.deque

The deque class provides an optimized rotate() method that physically rotates elements:

from collections import deque


def is_circularly_identical_deque(list_a: list, list_b: list) -> bool:
    """Check circular identity by rotating a deque."""
    if len(list_a) != len(list_b):
        return False

    if not list_a:
        return True

    d_a = deque(list_a)
    d_b = deque(list_b)

    for _ in range(len(list_a)):
        if d_a == d_b:
            return True
        d_a.rotate(1)

    return False


a = [1, 2, 3, 4]
b = [4, 1, 2, 3]

print(is_circularly_identical_deque(a, b))  # True

Finding the Rotation Offset

Return how many positions B is rotated from A:

from collections import deque


def find_rotation_offset(list_a: list, list_b: list) -> int | None:
    """Find the rotation offset, or None if not circularly identical."""
    if len(list_a) != len(list_b):
        return None

    if not list_a:
        return 0

    d_a = deque(list_a)
    d_b = deque(list_b)

    for offset in range(len(list_a)):
        if d_a == d_b:
            return offset
        d_a.rotate(1)

    return None


a = [1, 2, 3, 4]
b = [3, 4, 1, 2]

offset = find_rotation_offset(a, b)
print(f"Rotation offset: {offset}")  # Rotation offset: 2

Optimized Solution with Index Finding

If you only need to check once, find where list_b[0] appears in list_a:

def is_circularly_identical_optimized(list_a: list, list_b: list) -> bool:
    """Optimized check by finding potential starting points."""
    if len(list_a) != len(list_b):
        return False

    if not list_a:
        return True

    n = len(list_a)
    first_elem = list_b[0]

    for start in range(n):
        if list_a[start] == first_elem:
            match = True
            for j in range(n):
                if list_a[(start + j) % n] != list_b[j]:
                    match = False
                    break
            if match:
                return True

    return False

Working with Strings

For strings, the concatenation trick works directly:

def is_circular_string(s1: str, s2: str) -> bool:
    """Check if s2 is a rotation of s1."""
    if len(s1) != len(s2):
        return False
    return s2 in s1 + s1


print(is_circular_string("hello", "llohe"))  # True
print(is_circular_string("hello", "lohel"))  # True
print(is_circular_string("hello", "world"))  # False

Finding All Rotations

Generate all circular rotations of a list:

def get_all_rotations(lst: list) -> list:
    """Return all circular rotations of a list."""
    if not lst:
        return [[]]

    rotations = []
    for i in range(len(lst)):
        rotation = lst[i:] + lst[:i]
        rotations.append(rotation)

    return rotations


rotations = get_all_rotations([1, 2, 3])
for r in rotations:
    print(r)

Output:

[1, 2, 3]
[2, 3, 1]
[3, 1, 2]

Canonical Form for Grouping

To group circularly identical lists, compute a canonical (normalized) form:

def canonical_rotation(lst: list) -> tuple:
    """Return the lexicographically smallest rotation as a tuple."""
    if not lst:
        return tuple()

    n = len(lst)
    rotations = [tuple(lst[i:] + lst[:i]) for i in range(n)]
    return min(rotations)


lists = [
    [3, 1, 2],
    [1, 2, 3],
    [2, 3, 1],
    [4, 5, 6]
]

groups = {}
for lst in lists:
    key = canonical_rotation(lst)
    if key not in groups:
        groups[key] = []
    groups[key].append(lst)

for canonical, members in groups.items():
    print(f"{canonical}: {members}")

Output:

(1, 2, 3): [[3, 1, 2], [1, 2, 3], [2, 3, 1]]
(4, 5, 6): [[4, 5, 6]]

Method Comparison

Method Time Complexity Best For
Concatenation + sliding window O(n²) Standard lists
Deque rotation O(n²) Visualizing rotations
Index-based check O(n²) worst, O(n) average Few matching start elements
String in operator O(n) String rotations only

Summary

  • The concatenation trick (B in A+A) is the standard algorithmic approach.
  • Use sliding window comparison for lists since Python doesn't support list in list.
  • Never convert to strings: it causes false matches with numeric elements.
  • Use deque.rotate() when you need to physically manipulate rotations.
  • Compute canonical form when grouping circularly identical sequences.
Navigation