Can these tuples be arranged in a certain way?

Viewed 83

I'm working to code a program to take a list of tuples as input, and return something based on whether they can be arranged in a certain way. Normally I have an idea of how to go about a problem before coding but in this case I'm having a hard time coming up with a good way to do it.

The idea is to get input a list like this.. [(5, 2), (3, 5), (3, 3), (1, 3)] and verify whether it's possible to arrange in a way so the last number matches the beginning of the next tuple. So in this case it's possible, like: [(1, 3), (3, 3), (3, 5), (5, 2)]. So it's verified as true. The tuples can also be reversed.

I was thinking to iterate through the list and group together tuple pairs that work, but then what if they're not grouped in the right way to work with the rest of the pairs? Also it might be too time consuming.

Any ideas?

Thank you!

1 Answers

As mentioned in the comments, this is comparable to finding a Hamiltonian path in a graph of tuple elements with directed edges between tuples with matching first and last elements. Although it is an NP-complete problem (the Hamiltonian path, I don't know if a different approach to your problem could make it easier), it is easy to come up with brute-force algorithms for it. This is a rather naive recursive implementation:

def chained_list(lst):
    # List of rearranged elements
    chain = []
    # Flags to tell whether each item has been picked already
    picked = [False] * len(lst)
    # Loop to add all possible first elements (so the recursive function
    # can work on the assumption that there is a previous element)
    for i, item in enumerate(lst):
        # Add first element
        chain.append(item)
        # Mark as picked
        picked[i] = True
        # Attempt recursion
        _chain_list_rec(lst, picked, chain)
        # If we got a rearranged list finish
        if len(chain) == len(lst):
            return chain
        # Otherwise remove the selected first element
        picked[i] = False
        chain.pop()
    raise ValueError('cannot chain list')

def _chain_list_rec(lst, picked, chain):
    # Take previous value to match
    _, prev = chain[-1]
    # Iterate through items
    for i, (item, p) in enumerate(zip(lst, picked)):
        # If item is available and matches previous value
        if not p and item[0] == prev:
            # Add it and mark it as picked
            chain.append(item)
            picked[i] = True
            # Try remaining recursion
            _chain_list_rec(lst, picked, chain)
            # Check if we finished
            if len(chain) == len(lst):
                return
            # Undo adding if not finished
            picked[i] = False
            chain.pop()

print(chained_list([(5, 2), (3, 5), (3, 3), (1, 3)]))
# [(1, 3), (3, 3), (3, 5), (5, 2)]
print(chained_list([(5, 2), (3, 3), (1, 3)]))
# ValueError: cannot chain list

You can try to improve it in different ways, for example using a multiset instead of the list and a picked list of flags (assuming you want to support repeated elements, otherwise a set could do), using other data structures to search more quickly for the next potential item in the chain (e.g. a dict with key the first element and value a multiset of tuples starting with that element), or adding finish checks (check len(chain) == len(lst) at the beginning of the recursion to save the loop in the very last step). You can also check for feasibility of the current partial solution at every step in the recursion. Note that, for any partial solution: a) there must be at least one item starting with prev (the second value of the last item in chain) b) for any given value k, the number of available tuples starting with k must generally be equal to the number of available tuples ending with k, adjusting for k == prev and noting that there can be at most one k where there is an extra tuple finishing with k (which would be the last one). If these conditions don't hold, then that recursive path is not be feasible. You may think of other ways to improve the efficiency. In any case, however, the algorithm will become impossibly expensive to execute at one point or another, so keep in mind that this approach will only work for relatively small inputs.

Related