unordered combinations and protein synthesis

Nearly five years to the day, I wrote about unordered combinations within the context of faceting.

About 2 years ago, I was using resistance bands as my main strength training equipment. While I still use them, I mainly use dumbbells now.

Anyway, one of the issues I had with the bands was how to know all the combinations of different bands (I have about 5 different ones), sorted by level of resistance, low to high. In other words, I needed to calculate all the unordered combinations.

The idea was that once I could do a given number of reps and sets for a given type of lift, I'd increase the resistance. I had my workout in a spreadsheet, so having a sheet of all the unordered combinations gave me an easy way, via some spreadsheet formulas, to show me the next set of bands I needed to use for my next workout.

At the time, using some code I found using itertools, I got what I needed. But it really bothered me that I couldn't figure out how to create a function on my own that did what I needed.

So yesterday, after a couple of hours (or years!), I seem to have got something that's embarrassingly simple.

Basically, once I realized that my problem involved recursion, I was able to get rolling. Because rather than take a set, for instance ("a", b", "c", "d"), and return all the unordered combinations, I realized that I just needed a function that returned all the unordered combinations of length "n – 1" for a set of length "n". I could then just pass the newly calculated unordered combination itself to the function.

Sample code below. Feel free to share better implementations … or tell me this doesn't work at all.

A couple things to note:

1. While I think this will work fine (assuming it works at all) when the original set has repeated items – ("a", "b", "c", "c", "d"), it will incorrectly report what the correct number of unordered combinations are. I need to investigate the correct way of calculating this – by, you know, reading the page I cited in my original post.
2. This function returns an empty list as one of the unordered combinations. This is useless for calculating resistance band combinations – well, unless you're thinking about using body weight alone. But I included it due to something I remember reading in Discrete Mathematics And Its Applications (7th Edition, page 120), namely that "every nonempty set S is guaranteed to have at least two subsets, the empty set and the set S itself."
"""
Takes a list of unique "ITEMS" and reports:
1) number of items in ITEMS
2) number of unordered combinations within ITEMS
3) and prints what those combinations are (sorted by length of list).
"""

# establish the original list.
ITEMS = ["a", "b", "c", "d"]

# create an output list to hold all the unordered combinations;
# start by including the original list.
UNORDERED_COMBINATIONS = [ITEMS]

# calculate other combinations ...
def get_unordered_combinations(items):
""" Gets unordered combinations for lists whose length is (len(items) - 1). """
items_range = range(0, len(items))
for skip_pos in items_range: # get new combinations by excluding each item.
combination = [items[item_pos] for item_pos in items_range
if item_pos != skip_pos]
if combination not in UNORDERED_COMBINATIONS:
UNORDERED_COMBINATIONS.append(combination)
get_unordered_combinations(combination)

get_unordered_combinations(ITEMS)

# print reports.
print "the original list: ", ITEMS
print " - the number of ITEMS in the original list: ", len(ITEMS)
print " - the correct number of unordered lists within the original: ", (2**len(ITEMS))
print "   - the number of unordered lists actually returned: ", len(UNORDERED_COMBINATIONS)
print " - the unordered lists (preceded by the member list's length) ..."
print
RESULTS = [(len(x), x) for x in UNORDERED_COMBINATIONS] # i.e. (length of combination, combination).
for RESULT in sorted(RESULTS):
print RESULT

"""
OUTPUTS:

the original list:  ['a', 'b', 'c', 'd']
- the number of ITEMS in the original list:  4
- the correct number of unordered lists within the original:  16
- the number of unordered lists actually returned:  16
- the unordered lists (preceded by the member list's length) ...

(0, [])
(1, ['a'])
(1, ['b'])
(1, ['c'])
(1, ['d'])
(2, ['a', 'b'])
(2, ['a', 'c'])
(2, ['a', 'd'])
(2, ['b', 'c'])
(2, ['b', 'd'])
(2, ['c', 'd'])
(3, ['a', 'b', 'c'])
(3, ['a', 'b', 'd'])
(3, ['a', 'c', 'd'])
(3, ['b', 'c', 'd'])
(4, ['a', 'b', 'c', 'd'])
"""

Update, October 29, 2016: I wanted to see if I could clean this up a bit and cake it all within one function using nested functions and closures, so I've pasted that attempt below. This actually double-nests the main function. I just wanted to avoid writing a class.

def get_unordered_combinations(original_items):

unordered_combinations = [original_items]

def calc_unordered_combinations(items):
def calc_combinations():
items_range = range(0, len(items))
for skip_pos in items_range:
combination = [items[item_pos] for item_pos in items_range
if item_pos != skip_pos]
if combination not in unordered_combinations:
unordered_combinations.append(combination)
recurse = calc_unordered_combinations(combination)
recurse()
return calc_combinations

run = calc_unordered_combinations(original_items)
run()
unordered_combinations = [(len(x), x) for x in unordered_combinations]
return sorted(unordered_combinations)

# create original combination; get/print all unordered combinations.
ITEMS = ["a", "b", "c", "d"]
RESULTS = get_unordered_combinations(ITEMS)
for R in RESULTS:
print R

Update, October 29, 2016: I knew something useless was there. Here's the same thing, without the closure. It was completely unnecessary. Sometimes I read things and want to incorporate the idea without always asking myself if I really need to or not.

def get_unordered_combinations(original_items):

unordered_combinations = [original_items]

def calc_combinations(items):
items_range = range(0, len(items))
for skip_pos in items_range:
combination = [items[item_pos] for item_pos in items_range
if item_pos != skip_pos]
if combination not in unordered_combinations:
unordered_combinations.append(combination)
calc_combinations(combination)

calc_combinations(original_items)
unordered_combinations = [(len(x), x) for x in unordered_combinations]
return sorted(unordered_combinations)

# create original combination; get/print all unordered combinations.
ITEMS = ["a", "b", "c", "d"]
RESULTS = get_unordered_combinations(ITEMS)
for R in RESULTS:
print R
--------------