Calculate circular shift pairs in a list

Question:

A circular shift moves some of the digits in a number to the beginning of the number, and shifts all other digits forward to the next position. For example, all of the circular shifts of 564 are 564, 645, 456.

Lets say two numbers of equal length a and b are circular pairs if a can become b via circular shifting. Using the example above, the circular pairs of 564 are 645 and 456.

Given an array of positive integers nums, count the number of circular pairs i and j where 0 <= i < j < len(nums)

For example, circular_shifts([13, 5604, 31, 2, 13, 4560, 546, 654, 456]) should return 5. With the pairs being (13, 31), (13, 13), (5604, 4560), (31, 13), (546, 654).

I wrote a brute force solution that saves all circular shifts of a number in a dictionary shifts and for every number num in nums, I check all the following numbers and if a number num2 the same digits as num, I see if num2 is in num‘s circular shifts. If it is then I added this to the total count.

Unfortunately this algorithm runs too slowly and times out, I’m looking for a faster way to solve this problem can anyone think of optimizations, clever tricks, or other strategies to speed this up?

Asked By: Eric Hasegawa

||

Answers:

  1. Replace every number in the array with its greatest cyclic shift. 1234, for example, would become 4123
  2. Count the occurrences of each resulting number
  3. If a number occurs n times, that represents n(n-1)/2 cyclic shift pairs. Add them all up.
Answered By: Matt Timmermans

Not very elegant as not much time but the following should be faster as does not repeatedly access the list. Shifting is done on string for simplicity.

from collections import Counter

def big(num):
    max = num
    s = str(num)
    for _ in s:
        x = int(s[-1] + s[0:-1])
        if x > max:
            max = x
    return max
        
circs = [big(z) for z in mylist]

c = Counter(circs)

total = 0
for i in c:
    if c[i] > 1:
        total += c[i]
print(total)
Answered By: user19077881

Here’s my solution, where we

  1. iterate through the list
  2. rotate each number length-1 times
  3. and print only if the number is found in the remaining list
    numbers = [13, 5604, 31, 2, 13, 4560, 546, 654, 456]
    # shift_pairs = [(13,31),(13,13),(5604,4560),(31,13),(546,654)]
    pairsFound = 0
    for i in range(len(numbers)):
        number = numbers[i]
        length = len(str(number))
        numberToRotate = number
        shift_remaining = length - 1
        temp_list = numbers[i+1:]
        while(shift_remaining >= 0):
            t_remainder = numberToRotate % 10 # 4
            numberToRotate = numberToRotate // 10 # 560
            numberToRotate = numberToRotate + t_remainder * 10 **(length-1) # 4560
            # we should also check for length for cases like 4560, where on a shift we get 456
            if(numberToRotate in temp_list and len(str(numberToRotate)) == length):
                print("({},{})".format(number,numberToRotate))
                pairsFound += 1
            shift_remaining -= 1
    print("no of pairs:", pairsFound)

output

(13,31)
(13,13)
(5604,4560)
(31,13)
(546,654)
no of pairs: 5
Answered By: Mahesh Jamdade