简体   繁体   中英

sorting numbers with mix of switch and rotate in python

Justification first :)

Switch: Switch the marbles in positions 0 and 1.

Rotate: Move the marble in position 0 to position N - 1, and move all other marbles one space to the left (one index lower).

If there is a list of number (1,3,0,2) switch - rotate - switch will sort the numbers 3,1,0,2 - 1,0,2,3 - 0,1,2,3

But if we have (3,1,0,2), it never ends with switch - rotate - switch - rotate ... method.

Is there a better way to use both switch and rotate to efficiently get the sorted result?

I cannot now think of the most efficient way (meaning the way that uses the smallest number of rotations and switches) to sort any given list. But I can think of a way, given a list, to find the most efficient way.

Consider your problem to be a breadth-first search problem in a graph data structure. Consider a list to point directly to another list if it can be gotten from the current list by a single swap or single rotation. Do a breadth-first search until the sorted list is obtained. Then the path from the original list to the sorted list is "the most efficient way." You need not actually set up the graph data structure--this just gives the algorithm's idea.

I'll try to get some specific code here soon, but here is an outline. Start with a dictionary containing only the original list (which will need to be a tuple, so I'll start calling them tuples) as a key and None as the value. This dictionary contains the "already seen tuples" as keys, and for each key the value is the tuple that lead to that key. Also start with a queue (probably Python's deque ) that contains only the original tuple. This is the "seen but not yet processed" queue. Then run a loop: pop a tuple off the queue, check if it is the sorted tuple, then for each tuple reachable by a single switch or rotation check if it was already seen, add it to both the dictionary and the queue. Eventually you will reach the sorted tuple (if the original tuple was defined correctly). Use the "already seen" dictionary to print the path from the sorted tuple back to the original tuple.

Here is code based on that algorithm. Further optimizations could be done, such as in-lining the switched_or_rotated routine or checking for the target tuple when it is first seen rather than waiting for when it is processed.

from collections import deque

# Constant strings: ensure they are the same length for pretty printing
START  = 'Start: '
SWITCH = 'Switch:'
ROTATE = 'Rotate:'

def switched_or_rotated(atuple):
    """Generate the tuples reachable from the given tuple by one switch
    or rotation, with the action that created each tuple.
    """
    yield (atuple[1::-1] + atuple[2:], SWITCH)  # swap first two items
    yield (atuple[1:] + atuple[:1], ROTATE)  # rotate first item to the end

def sort_by_switch_and_rotate(iter):
    """Sort a finite, sortable iterable by repeatedly switching the
    first two items and/or rotating it left (position 0 to the end, all
    others to one index lower). Print a way to do this with the
    smallest number of switches and/or rotations then return the number
    of steps needed. 

    Based on <https://stackoverflow.com/questions/54840758/
    sorting-numbers-with-mix-of-switch-and-rotate-in-python>
    """
    # Initialize variables
    original = tuple(iter)
    targettuple = tuple(sorted(original))
    alreadyseen = {original: None}  # tuples already seen w/ previous tuple
    actions = {original: START}  # actions that got each tuple
    notprocessed = deque()  # tuples seen but not yet processed
    # Do a breadth-first search for the target tuple
    thistuple = original
    while thistuple!= targettuple:
        for nexttuple, nextaction in switched_or_rotated(thistuple):
            if nexttuple not in alreadyseen:
                alreadyseen[nexttuple] = thistuple
                actions[nexttuple] = nextaction
                notprocessed.append(nexttuple)
        thistuple = notprocessed.popleft()
    # Print the path from the original to the target
    path = []
    while thistuple:
        path.append(thistuple)
        thistuple = alreadyseen[thistuple]
    print('\nHow to sort a list in {} steps:'.format(len(path)-1))
    for thistuple in reversed(path):
        print(actions[thistuple], thistuple)
    # Return the minimal number of steps
    return len(path) - 1

Here is test code for your two examples and some additional examples.

# Example tuples from the questioner
assert sort_by_switch_and_rotate((1, 3, 0, 2)) == 3
assert sort_by_switch_and_rotate((3, 1, 0, 2)) == 2

# Test tuples
assert sort_by_switch_and_rotate((0, 1, 2, 3)) == 0  # identity
assert sort_by_switch_and_rotate((1, 0, 2, 3)) == 1  # one switch
assert sort_by_switch_and_rotate((3, 0, 1, 2)) == 1  # one rotation
assert sort_by_switch_and_rotate((1, 2, 3, 0)) == 3  # max rotations
assert sort_by_switch_and_rotate((1, 0, 3, 2)) == 6  # from @MattTimmermans

The printout from that is

How to sort a list in 3 steps:
Start:  (1, 3, 0, 2)
Switch: (3, 1, 0, 2)
Rotate: (1, 0, 2, 3)
Switch: (0, 1, 2, 3)

How to sort a list in 2 steps:
Start:  (3, 1, 0, 2)
Rotate: (1, 0, 2, 3)
Switch: (0, 1, 2, 3)

How to sort a list in 0 steps:
Start:  (0, 1, 2, 3)

How to sort a list in 1 steps:
Start:  (1, 0, 2, 3)
Switch: (0, 1, 2, 3)

How to sort a list in 1 steps:
Start:  (3, 0, 1, 2)
Rotate: (0, 1, 2, 3)

How to sort a list in 3 steps:
Start:  (1, 2, 3, 0)
Rotate: (2, 3, 0, 1)
Rotate: (3, 0, 1, 2)
Rotate: (0, 1, 2, 3)

How to sort a list in 6 steps:
Start:  (1, 0, 3, 2)
Switch: (0, 1, 3, 2)
Rotate: (1, 3, 2, 0)
Rotate: (3, 2, 0, 1)
Switch: (2, 3, 0, 1)
Rotate: (3, 0, 1, 2)
Rotate: (0, 1, 2, 3)

I don't know if this gives an answer to your question, which I found it very tricky.


I wrote a class to be used in a loop:

 class Marbles: def __init__(self, marbles): self.marbles = marbles self.len = len(marbles) def switch(self): self.marbles[0], self.marbles[1] = self.marbles[1], self.marbles[0] if self.is_sorted(): raise StopIteration return self def rotate(self): self.marbles = self.marbles[1:] + [self.marbles[0]] if self.is_sorted(): raise StopIteration return self def is_sorted(self): return all(self.marbles[i] <= self.marbles[i+1] for i in range(self.len-1)) def show(self): print(self.marbles)

When after a move marbles are sorted it throws the exception StopIteration , so the loop can break.

So, for your example (1,3,0,2) :

tested = []
original = [3,1,0,2]
marbles = Marbles(original)
while True:
  try:
    marbles.switch().show()
    marbles.rotate().show()
  except: break
  if original in tested: break
  tested.append(marbles.marbles)
print(marbles.is_sorted())
marbles.show()

print("-"*20)

tested = []
original = [3,1,0,2]
marbles = Marbles(original)
while True:
  try:
    marbles.rotate().show()
    marbles.switch().show()
  except: break
  if original in tested: break
  tested.append(marbles.marbles)
print(marbles.is_sorted())
marbles.show()

Now you can write a couple of loops using brute force, where the order of actions is swapped (in this case I considered the rule to be an alternate sequence of switch and rotate):

# [1, 3, 0, 2]
# [3, 0, 2, 1]
# [0, 3, 2, 1]
# [3, 2, 1, 0]
# [2, 3, 1, 0]
# [3, 1, 0, 2]
# [1, 3, 0, 2]
# [3, 0, 2, 1]
# False
# [3, 0, 2, 1]
# --------------------
# [1, 0, 2, 3]
# True
# [0, 1, 2, 3]

This returns

# [1, 3, 0, 2] # [3, 0, 2, 1] # [0, 3, 2, 1] # [3, 2, 1, 0] # [2, 3, 1, 0] # [3, 1, 0, 2] # [1, 3, 0, 2] # [3, 0, 2, 1] # False # [3, 0, 2, 1] # -------------------- # [1, 0, 2, 3] # True # [0, 1, 2, 3]

Pick one number at the start that you will never switch to represent an unmovable start/end in the list. No matter which number you pick, your simple algorithm of switching out-of-order elements and rotating will then always work.

Note that "out of order" gets a bit complicated if you don't pick the smallest or largest element, since the correct order is cyclic. The element smaller than the ones you pick go after the larger ones.

Try all of the choices to see which one gives the fastest result.

eg:

Don't switch 0:

3,1,0,2 - 1,3,0,2 - 3,0,2,1 - 0,2,1,3 - 2,1,3,0 - 1,2,3,0 - 2,3,0,1 - 3,0,1,2 - 0,1,2,3

Don't switch 1:

3,1,0,2 - 1,0,2,3 - 0,2,3,1 - 2,0,3,1 - 0,3,1,2 - 3,0,1,2 - 0,1,2,3

Don't switch 2:

3,1,0,2 - 1,0,2,3 - 0,1,2,3

Don't switch 3:

3,1,0,2 - 1,0,2,3 - 0,1,2,3

EDIT: This doesn't find the best when all best solutions require all elements to participate in swaps. It does always find a solution, though, and it's polynomial time.

Python provides best way of sorting the list by using the list sort inbuilt function. For example:

my_list=[3,1,0,2]
my_list.sort()
print(my_list)

output: [0,1,2,3]

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM