Ваша проблема хитрая, потому что есть некоторые крайние случаи, о которых нужно подумать:
- Строки с повторяющимися символами (т.е. как бы вы перетасовали "aaaab"?)
- Как сделатьВы измеряете цепочечные перестановки символов или реорганизуете блоки?
В любом случае метрика, заданная для перемешивания строк до определенного процента, вероятно, будет той же, которую вы используете в своем алгоритме, чтобы увидеть, насколько близкоони есть.
Мой код для перемешивания n
символов:
import random
def shuffle_n(s, n):
idx = range(len(s))
random.shuffle(idx)
idx = idx[:n]
mapping = dict((idx[i], idx[i-1]) for i in range(n))
return ''.join(s[mapping.get(x,x)] for x in range(len(s)))
В основном выбирает n
позиций для случайного обмена, а затем обменивает каждую из них на следующую в списке... Таким образом, это гарантирует, что обратные перестановки не генерируются, и точно n
символы меняются местами (если есть повторяющиеся символы, неудача).
Объясненный прогон с 'строкой', 3 в качестве ввода:
idx is [0, 1, 2, 3, 4, 5]
we shuffle it, now it is [5, 3, 1, 4, 0, 2]
we take just the first 3 elements, now it is [5, 3, 1]
those are the characters that we are going to swap
s t r i n g
^ ^ ^
t (1) will be i (3)
i (3) will be g (5)
g (5) will be t (1)
the rest will remain unchanged
so we get 'sirgnt'
Недостаток этого метода в том, что он не генерирует все возможные варианты, например, он не может сделать 'gnrits' из 'string'.Это можно исправить, перетасовав разделы индексов следующим образом:
import random
def randparts(l):
n = len(l)
s = random.randint(0, n-1) + 1
if s >= 2 and n - s >= 2: # the split makes two valid parts
yield l[:s]
for p in randparts(l[s:]):
yield p
else: # the split would make a single cycle
yield l
def shuffle_n(s, n):
idx = range(len(s))
random.shuffle(idx)
mapping = dict((x[i], x[i-1])
for i in range(len(x))
for x in randparts(idx[:n]))
return ''.join(s[mapping.get(x,x)] for x in range(len(s)))