Borrowing from this answer and adjusting it for the use case you have where you can enter just the single characters (ab
, abc
, etc.), ensuring we keep order of AaBbCcDd...
, and breaking the lists into punnett square quadrants following the standard distribution so that
AA, Aa
Aa, aa
and
AABB, AABb, AaBB, AaBb
AABb, AAbb, AaBb, Aabb
AaBB, AaBb, aaBB, aaBb
AaBb, Aabb, aaBb, aabb
will always be output in the proper order and quadrant (and following for any size input).
def punnett(ins):
# first get the proper order of AaBbCc... based on input order not alphabetical
order = ''.join(chain.from_iterable((x.upper(), x.lower()) for x in ins))
# now get your initial square output by sorting on the index of letters from order
# and using a lot of the same logic as other answers (and the linked source)
ps = [''.join(sorted(''.join(e), key=lambda word: [order.index(c) for c in word]))
for e in product(*([''.join(e) for e in product(*e)]
for e in zip(
[list(v) for _, v in groupby(order, key = str.lower)],
[list(v) for _, v in groupby(order, key = str.lower)])))]
outp = set()
outx = []
# Now to get your quadrants you need to do numbers
# from double the length of the input
# to the square of the length of that double
for x in range(len(ins)*2, (len(ins)*2)**2, len(ins)):
# using this range you need the numbers from your x minus your double (starting 0)
# to your x minus the length
# and since you are iterating by the length then will end up being your last x
# Second you need starting at x and going up for the length
# so for input of length 2 -> x=4 -> 0, 1, 4, 5
# and next round -> x=6 -> 2, 3, 6, 7
temp = [i for i in range(x - len(ins)*2, x - len(ins))] + [i for i in range(x, x+len(ins))]
# and now since we need to never use the same index twice, we check to make sure none
# have been seen previously
if all(z not in outp for z in temp):
# use the numbers as indexes and put them into your list
outx.append([ps[i] for i in temp])
# add each individually to your set to check next time if we have seen it
for z in temp:
outp.add(z)
return outx
So output (plus new lines to make it look like a standard matrix):
>>> punnett('ab')
[['AABB', 'AABb', 'AaBB', 'AaBb'],
['AABb', 'AAbb', 'AaBb', 'Aabb'],
['AaBB', 'AaBb', 'aaBB', 'aaBb'],
['AaBb', 'Aabb', 'aaBb', 'aabb']]
>>> punnett('abc')
[['AABBCC', 'AABBCc', 'AABBCc', 'AABbCc', 'AABbcc', 'AABbCC'],
['AABBcc', 'AABbCC', 'AABbCc', 'AABbCc', 'AABbCc', 'AABbcc'],
['AAbbCC', 'AAbbCc', 'AAbbCc', 'AaBBCc', 'AaBBcc', 'AaBbCC'],
['AAbbcc', 'AaBBCC', 'AaBBCc', 'AaBbCc', 'AaBbCc', 'AaBbcc'],
['AaBbCC', 'AaBbCc', 'AaBbCc', 'AabbCc', 'Aabbcc', 'AaBBCC'],
['AaBbcc', 'AabbCC', 'AabbCc', 'AaBBCc', 'AaBBCc', 'AaBBcc']]
There are certainly some efficiencies to be gained in this code to make it shorter and you could generate your initial ps
using any of the methods from the other posters if you wanted, assuming the generate them in the same order. You would still need to apply the ''.join(sorted(XXX, key...))
method to them to get the output you are looking for.