2

Similar question has been asked in JAVA, but can someone help me in improving the code: And explain what would be the time complexity and space complexity of my code. My code checks if two arrays are rotated version of each other:

list1 = [1, 2, 3, 4, 5, 6, 7]

list2b = [4, 5, 6, 7, 1, 2, 3]

is_rotation(list1, list2b) should return True.

list2c = [4, 5, 6, 9, 1, 2, 3]

is_rotation(list1, list2c) should return False.

My code:

def is_rotation (list1,list2):

    i = 0
    j = 0
    k = 0

    result = True

    if len(list1) != len(list2):
        return False  

    while i < len(list1) -1 and j < len(list1) -1:

        if list1[i] == list2[j]:
            i = i +1
            j = j +1
            break
        elif list1[i] > list2[j]:
            j = j +1
        else:
            i = i +1
    else:
        return False

    for l in range(i,len(list1)):

        if i == j:
            if list1[i] != list2[j]: 
                return False
        elif list1[i] != list2[j] or list2[i] != list1[k]:
            return False
        else:
            i = i +1
            j = j +1
            k = k +1

    return result
prabh
  • 336
  • 1
  • 3
  • 16
  • Some comments to describe what task the loops are suppose to accomplish would help – David Collins Mar 01 '20 at 00:32
  • *If the values are unique*, consider this simplification: i2 = index of l1[0] in l2; rotated iff, for x in 0 up to len l1, where l1[x] == l2[(i2 + x) % len l1] - that can be written in two simple loops (or less). That is, with a unique set of values, it’s just comparing sequence equality given a “bias” for the second list start that was previously selected. – user2864740 Mar 01 '20 at 00:32
  • The above can be modified to work with non-unique values by trying all occurrences of l1[0] in l2 as the “bias” until one matches. – user2864740 Mar 01 '20 at 00:39
  • A fast way to do this, O(n), would be to use some fast algorithm for exact string matching, like Knuth-Morris-Pratt, but applied to list items instead of letters. – Gassa Mar 01 '20 at 01:14
  • An easy way to do it is to just look at all possible rotations. A rotation of list `a` by `k` positions is simply `a[k:] + a[:k]`. – Gassa Mar 01 '20 at 01:15
  • Does this answer your question? [Efficient way to rotate a list in python](https://stackoverflow.com/questions/2150108/efficient-way-to-rotate-a-list-in-python) – AMC Mar 01 '20 at 01:53

4 Answers4

2

A bit hacky way:

def is_rotation(lst1, lst2):
    if(len(lst1)==len(lst2)):
        return (str(lst1)[1:-1] in str(lst2+lst2)) & (str(lst2)[1:-1] in str(lst1+lst1)) 
    else:
        return False

How does it work:

(1) check if both lists are of the same length, if not return False

(2) if they do, convert first list to string, drop the most outer brackets (by dropping first and last character- you can do any brackets there, not only square ones, it can be a tuple too).

(3) lst2+lst2 will return all the elements of lst2 duplicated in sequence (so one lst2 after another). Then converted to string it will just return its string format of list

(4) as per the comments- in order to handle the corner cases - we should check both ways, since if lst1 is rotated version of lst2 then lst2 is the rotated version of lst1

Tests

print(is_rotation([561, 1, 1, 1, 135], [1, 1, 1, 1, 1]))
#outputs False
print(is_rotation([[1,2,3,4], 2, 3, 4], [1, 2,3,4]))
#outputs False
print(is_rotation([1, 2, 3, 4, 5], [4, 5, 1, 2, 3]))
#outputs True
Grzegorz Skibinski
  • 12,624
  • 2
  • 11
  • 34
  • Not exactly: for example, [1, 1, 1, 1, 1] will incorrectly be seen as a rotation of [54321, 1, 1, 1, 12345]. – Gassa Mar 01 '20 at 01:13
  • @Amadan Why is it the best way to do this? – AMC Mar 01 '20 at 04:35
  • Definitely not the best way: fails for some patterns of values (as @Gassa pointed out), requires costly transformations to strings and more extra memory than even copying the whole lists would require. – Alain T. Mar 01 '20 at 04:43
  • Thanks guys- please have a look now - this should do the trick for the cases you mentioned. – Grzegorz Skibinski Mar 01 '20 at 08:46
0

I though in another way to do this:

def is_rotation (list1,list2):
    if len(list1) != len(list2):
        return False

    for i in range(len(list1)):
        if list1[i:] + list1[:i] == list2:
            return True

    return False

1) Test if both have same length.

2) The loop will just rotate the list all the possibilities and check if in one of them both are equal..

I don't know if this is the best way, but is simple to understand ;)

Joao Ponte
  • 160
  • 3
  • 8
0

For a somewhat more explicit approach, you can generate all the rotations of a given list using itertools, one by one, with this:

import itertools as it
def get_rotations(lst1):
    foo = it.cycle(lst1)
    for y in range(len(lst1)):
        result = []
        for x in range(len(lst1)):
            result.append(next(foo))
        yield result
        next foo

Then you can do:

def is_rotated(L1, L2) -> bool:
    bar = get_rotations(L1)
    While True:
        try:
            if next(bar) == L2;
                return True
        except StopIteration:
                return False

Aside: I think this breaks all the traditional views on using exceptions to control the normal flow of program logic, however... It feels wrong, somehow (background in C++, where we were repeatedly told to never do this kind of thing.). Is it Pythonic?

neutrino_logic
  • 1,289
  • 1
  • 6
  • 11
  • Instead of the try-except, you could simply iterate over `far`, and use an else statement in place of the except to return false. – AMC Mar 01 '20 at 01:46
0

You can use cycle from itertools to optimize the number of comparisons and avoid creating additional copies of the data. (i.e. O(1) space in O(n) time)

Here's an example of a function that will return the rotation offset if the two lists are a rotation of each other or None if they do not match. The logic will never need more than 2N comparisons to determine the offset.

from itertools import cycle
def getRotation(listA,listB):
    if len(listA) != len(listB): return None
    unmatched,offset = len(listA),0
    iterA,iterB      = cycle(listA),cycle(listB)
    a = next(iterA)
    while unmatched and offset<len(listA):
        b = next(iterB)
        if a==b:
            unmatched -= 1
            a = next(iterA)
        else:
            unmatched = len(listA)
            offset   += 1
    if unmatched: return None
    return offset

outpu:

list1 = [1, 2, 3, 4, 5, 6, 7]
list2b = [4, 5, 6, 7, 1, 2, 3]
print(getRotation(list1,list2b)) # 4 (rotation to the right)

You can adapt it to simply return True or False if needed.

It can also be easily adapted to check if a list can be produced by cycling over the other. (e.g. [2,3,1,2,3,1,2,3,1,2] can be produced by cycling [1,2,3]). I did not include that feature in the example to avoid confusing the issue.

Alain T.
  • 40,517
  • 4
  • 31
  • 51