You can use a backtracking algorithm, keeping track of the previous two characters an the counts of pairs and triplets of characters seen so far. Then just recursively generate all the valid sequences. To make this more random, you can sample/shuffle the order in which characters are tried in each recursive call.
If you implement this as a generator (e.g. in Python), you can use it to generate all, or just the next such combination. Otherwise, just return the first solution that you find.
Example in Python:
import collections, random
def backtrack(available, n, counts, last=None, lastlast=None):
if n == 0:
yield []
else:
for c in random.sample(list(available), len(available)):
# check constraints
if available[c] == 0: continue
if c == last: continue
if last is not None and counts[c+last] > 1: continue
if lastlast is not None and counts[c+last+lastlast] > 0: continue
# update counts
available[c] -= 1
if last: counts[c+last] += 1
if lastlast: counts[c+last+lastlast] += 1
# recursive call to get remainder
for rest in backtrack(available, n-1, counts, c, last):
yield [c] + rest
# reset counts
available[c] += 1
if last: counts[c+last] -= 1
if lastlast: counts[c+last+lastlast] -= 1
Example:
lst = "A B C D E F A B C D E F A B C D E F A B C D E F".split()
print(next(backtrack(collections.Counter(lst), 6, collections.Counter())))
print(next(backtrack(collections.Counter(lst), 6, collections.Counter())))
print(next(backtrack(collections.Counter(lst), 6, collections.Counter())))
res = list(backtrack(collections.Counter(lst), 6, collections.Counter()))
print(len(res))
Output:
['A', 'C', 'B', 'A', 'E', 'A']
['A', 'F', 'A', 'E', 'D', 'A']
['E', 'D', 'E', 'F', 'E', 'B']
18360
However, depending on your list and the number of elements to take from it, it might also work to just generate random samples from the list and checking the constraints until you find one that works.
A B A B D A Bwould not be allowed. What aboutA B A B D B A? That is, does the order of items in the dyad matter?