To formalize a bit: say we have an ordered list of sets, such that each set in the list is a subset of the set S. Next, we say if a consecutive pair of sets in the ordering has an element included in one set, and not the other, the transition count for the element, for this pair, is 1. Otherwise, this transition count is 0. We will also say that there exists a total ordering T for the elements in set S. How would you go about sorting the list of sets such that the total transition counts (for every consecutive pair in the ordered list of sets) per element is prioritized in the order provided by the ordering of the set S?
For instance, say the sets we want to order are the power set of a given set (all subsets), less an arbitrary number of sets. For my application, this represents generating remaining experiments with n explanatory variables toggled on or off (represented by set inclusion), for which some experiments may already have been conducted. The ordering of the variables used in the sorting is the ordering of how costly it is to toggle a variable, and we are trying to minimize the total transition cost for the set of remaining experiments.
from itertools import chain, combinations
def powerset(iterable):
"powerset([1,2,3]) --> () (1,) (2,) (3,) (1,2) (1,3) (2,3) (1,2,3)"
s = list(iterable)
return chain.from_iterable(combinations(s, r) for r in range(len(s)+1))
# fields ordered by decreasing difficulty to swap
difficulty_ordering = ['a', 'b', 'c', 'd']
field_powerset = list(powerset(difficulty_ordering))
[print(subset) for subset in field_powerset]
which would be:
()
('a',)
('b',)
('c',)
('d',)
('a', 'b')
('a', 'c')
('a', 'd')
('b', 'c')
('b', 'd')
('c', 'd')
('a', 'b', 'c')
('a', 'b', 'd')
('a', 'c', 'd')
('b', 'c', 'd')
('a', 'b', 'c', 'd')
and if we arbitrarily remove some:
field_powerset = random.sample(field_powerset, random.randrange(len(field_powerset)))
we get:
('a', 'b', 'd')
('a', 'd')
('b',)
()
('b', 'c', 'd')
('a', 'b', 'c', 'd')
('a', 'c')
('b', 'c')
('b', 'd')
('c', 'd')
('a', 'b', 'c')
('d',)
now how do we go about getting the sorted result of something roughly similar to this, using difficulty_ordering?
()
('c', 'd')
('d',)
('b',)
('b', 'c')
('b', 'c', 'd')
('b', 'd')
('a', 'b', 'd')
('a', 'b', 'c', 'd')
('a', 'b', 'c')
('a', 'c')
('a', 'd')
# 1 a transition, 2 b transitions, 6 c transitions, 5 d transitions
I wouldn't want to reimplement an entire sort if possible, but I'm unsure if this idea could even be implemented with python's "key function" for sorting.
But after solving the problem manually, it feels like there's a heuristic I can follow to get a good solution:
It seems you need to first "lock in" the ranges of sets, by first minimizing the amount of transitions of the first field. So first you subdivide (subdivisions represented by #) the lists into ones with 'a' and ones without
('b',)
()
('b', 'c', 'd')
('b', 'c')
('b', 'd')
('c', 'd')
('d',)
#
('a', 'b', 'd')
('a', 'd')
('a', 'b', 'c', 'd')
('a', 'c')
('a', 'b', 'c')
and going forward you can only move these elements within their range. then you move on to 'b' and further subdivide these ranges into two with and without, but now it needs to go "without b, with b, with b without b" to minimize transitions fully
()
('c', 'd')
('d',)
#
('b',)
('b', 'c', 'd')
('b', 'c')
('b', 'd')
#
('a', 'b', 'd')
('a', 'b', 'c', 'd')
('a', 'b', 'c')
#
('a', 'd')
('a', 'c')
then c, further flipping with c without c per section
()
('d',)
#
('c', 'd')
#
('b', 'c', 'd')
('b', 'c')
#
('b',)
('b', 'd')
#
('a', 'b', 'd')
#
('a', 'b', 'c', 'd')
('a', 'b', 'c')
#
('a', 'c')
#
('a', 'd')
and last, 'd', but it gets a little different here, as we would think we just swap the order of with/without per subdivision we are subdividing. But at a certain point you might not want to follow this as concretely, in the case of the 3rd subdivision above. If we break our rules a bit we get to a fully optimal solution here:
()
#
('d',)
#
('c', 'd')
#
('b', 'c', 'd')
#
('b', 'c')
#
('b',)
#
('b', 'd')
#
('a', 'b', 'd')
#
('a', 'b', 'c', 'd')
#
('a', 'b', 'c')
#
('a', 'c')
#
('a', 'd')
Choosing not to swap the with/without ordering in that subdivision gets you a lower total cost ordering. So essentially, it seems you need to match the previous set's inclusion of your current element when deciding the with/without ordering of your current subdivision. And this part makes me think doing recursion wouldn't be a great way of solving the problem. But, I'm not sure that I could formulate these thoughts and rules into a python dynamic programming algorithm fully at present.