Search code examples
algorithmlanguage-agnosticcombinatorics

Efficiently generating "subtraction chains"


I posted another question earlier if you want some context. It appears that I was on the wrong path with that approach.

Addition chains can be used to minimize the number of multiplications needed to exponentiate a number. For example, a7 requires four multiplications. Two to compute a2=a×a and a4=a2×a2, and another two to compute a7=a4×a2×a.

Similarly, I'm trying to generate all of the possible "subtraction chains" for a set of numbers. For example, given the set of numbers {1, 2, 3}, I'm trying to generate the following permutations.

{1, 2, 3}

{1, 2, 3}, {1, 2}
{1, 2, 3}, {1, 2}, {1}
{1, 2, 3}, {1, 2}, {2}
{1, 2, 3}, {1, 2}, {1}, {2}

{1, 2, 3}, {1, 3}
{1, 2, 3}, {1, 3}, {1}
{1, 2, 3}, {1, 3}, {3}
{1, 2, 3}, {1, 3}, {1}, {3}

{1, 2, 3}, {2, 3}
{1, 2, 3}, {2, 3}, {2}
{1, 2, 3}, {2, 3}, {3}
{1, 2, 3}, {2, 3}, {2}, {3}

{1, 2, 3}, {1, 2}, {1, 3}
{1, 2, 3}, {1, 2}, {1, 3}, {1}
{1, 2, 3}, {1, 2}, {1, 3}, {2}
{1, 2, 3}, {1, 2}, {1, 3}, {3}
{1, 2, 3}, {1, 2}, {1, 3}, {1}, {2}
{1, 2, 3}, {1, 2}, {1, 3}, {1}, {3}
{1, 2, 3}, {1, 2}, {1, 3}, {2}, {3}
{1, 2, 3}, {1, 2}, {1, 3}, {1}, {2}, {3}

# and so on...

Where each element in the permutation (besides {1, 2, 3}) can be found by removing a single element from another set in the permutation.

For example, the permutation {1, 2, 3}, {1} is invalid because {1} can not be constructed by removing a single element from {1, 2, 3}.

Is there a known algorithm to find this subset of the power set of a power set? My implementation will be in Python, but the question is language agnostic. Also, I don't actually want the permutations which contain a set with a single element (e.g. {1, 2, 3}, {1, 2}, {1}) because they corresponds to a "dictator" case which is not of interest.


Solution

  • An algorithm to generate all those lists as you describe it could work as follows: For each set in the current list, create a copy, remove one element, add it to the list, and call the algorithm recursively. You also have to make sure not to generate duplicates, which could by done by ensuring that the new list is "smaller" (by length or pairwise comparison of the (sorted) elements) than the previous one.

    Here's an implementation in Python, as a generator function, without much optimization. This seems to work pretty well now, generating all the subsets without any duplicates.

    def generate_sets(sets, min_num=2):
        yield sets
        added = set() # new sets we already generated in this iteration
        for set_ in sets:
            # only if the current set has the right length
            if min_num < len(set_) <= len(sets[-1]) + 1:
                for x in set_:
                    # remove each element in turn (frozenset so we can put in into added)
                    new = set_.difference({x})
                    # prevent same subset being reachable form multiple sets
                    frozen = frozenset(new)
                    if frozen not in added:
                        added.add(frozen)
                        # recurse only if current element is "smaller" than last
                        if (len(new), sorted(new)) < (len(sets[-1]), sorted(sets[-1])):
                            for result in generate_sets(sets + [new], min_num):
                                yield result
    

    For generate_sets([{1,2,3}], min_num=2) this generates the following lists:

    [{1, 2, 3}]
    [{1, 2, 3}, {2, 3}]
    [{1, 2, 3}, {2, 3}, {1, 3}]
    [{1, 2, 3}, {2, 3}, {1, 3}, {1, 2}]
    [{1, 2, 3}, {2, 3}, {1, 2}]
    [{1, 2, 3}, {1, 3}]
    [{1, 2, 3}, {1, 3}, {1, 2}]
    [{1, 2, 3}, {1, 2}]
    

    For generate_sets([{1,2,3}], 1), a total of 45 lists of sets are generated.

    However, I fail to see the connection to your previous question: Shouldn't {1, 2, 3}, {1, 2}, {1, 2, 3}, {1, 3}, and {1, 2, 3}, {2, 3} all be considered equivalent?