Search code examples
pythoncyclereorderlist

arrange items respecting order


I have a list of elements that I need to rearrange cyclically such that I keep their order. The problem seems quite simple but I can't figure out a smart way to code it. Say you have elements

1 2 3 4 o o o 5 6 7

The o's are always contiguous in the array, but I need to change this array so that the o's (which are not necessarily of a different type) are last in a cyclic way:

5 6 7 1 2 3 4 o o o

The problem is that the o's may be also contiguous in a cyclic way. For example,

o o 1 2 3 4 5 6 7 o

Is there a smart way to do this? I've been looking at cycle from itertools, but as of now I don't have a working implementation as what I did is unable to handle the last case.

UPDATE

I have a first working implementation:

def arrange2(nodes, contiguous):

    arranged = []
    size = len(nodes)

    if nodes[0] in contiguous:

        # obtain the id of the last interface node in nodes
        id = None
        for i in range(1, len(nodes)):
            if nodes[i] not in contiguous:
                id = i
                break

        # copy nodes to new list starting from the first node past id
        for i in range(id, id + size):
            arranged += [nodes[i % size]]
    else:

        # obtain the id of the last interface node in nodes
        id = None
        for i in range(size - 1, -1, -1):
            if nodes[i] in contiguous:
                id = i
                break

        # copy nodes to new list starting from the first node past id
        for i in range(id+1, id + size+1):
            arranged += [nodes[i % size]]

    return arranged


print(arrange2([1, 2, 3, 4, 5, 6, 7, 8, 9, 10], [5, 6]))

This prints [7, 8, 9, 10, 1, 2, 3, 4, 5, 6]


Solution

  • Ok, basing on you implementation I have this:

    def arrange(nodes: list, contigious: list) -> list:
        first_contigious = nodes.index(contigious[0])
        last_contigious = nodes.index(contigious[-1])
    
        if first_contigious < last_contigious:
            # Normal situation
            return nodes[last_contigious+1:] + nodes[:first_contigious] + contigious
        else:
            # The contigious part is cycling
            return nodes[last_contigious+1:first_contigious] + contigious
    

    EDIT, after clarification in the comments, that the contigious collection doesn't have to be ordered I have this:

    def arrange(nodes: list, contigious: set) -> list:
        # Make sure that contigious is a set
        contigious = set(contigious)
    
        # Return if all nodes are in contigious or nodes are empty
        if len(contigious.intersection(nodes)) == len(nodes) or not len(nodes):
            return nodes
    
        if nodes[0] in contigious and nodes[-1] in contigious:
            # The contigious part is split and present on the beggining and the 
            # end of the nodes list
            cut = next(i for i, x in enumerate(nodes) if x not in contigious)
            # I move the nodes from the beggining to the end
            return nodes[cut:] + nodes[:cut]
        else:
            # The contigious part is somewhere in the middle of the nodes list
            # I need to find the end of contigious sequence
            cut = next(i for i, x in enumerate(reversed(nodes)) if x in contigious)
            cut = len(nodes) - cut
            return nodes[cut:] + nodes[:cut]
    

    Note: It's your job to make sure that the contigious elements are indeed next to each other and not scattered in 3 or more groups.