如何在Python中实现随机的部分洗牌?

7 投票
5 回答
3774 浏览
提问于 2025-04-17 07:28

我想要一个在Python中实现的部分 shuffle(洗牌)功能,而不是完全的洗牌。

举个例子:比如“string”这个词,经过部分洗牌后应该变成“stnrig”,而不是“nrsgit”。

如果我能定义一个特定的“百分比”,来决定有多少字符需要被重新排列,那就更好了。

这样做的目的是为了测试字符串比较算法。我想找出一个“洗牌百分比”,超过这个百分比后,我的算法会把两个(洗牌过的)字符串标记为完全不同。

更新:

这是我的代码,欢迎提出改进意见!

import random

percent_to_shuffle = int(raw_input("Give the percent value to shuffle : "))
to_shuffle = list(raw_input("Give the string to be shuffled : "))

num_of_chars_to_shuffle = int((len(to_shuffle)*percent_to_shuffle)/100)

for i in range(0,num_of_chars_to_shuffle):
    x=random.randint(0,(len(to_shuffle)-1))
    y=random.randint(0,(len(to_shuffle)-1))
    z=to_shuffle[x]
    to_shuffle[x]=to_shuffle[y]
    to_shuffle[y]=z

print ''.join(to_shuffle)

5 个回答

1
import random

def partial_shuffle(a, part=0.5):
    # which characters are to be shuffled:
    idx_todo = random.sample(xrange(len(a)), int(len(a) * part))

    # what are the new positions of these to-be-shuffled characters:
    idx_target = idx_todo[:]
    random.shuffle(idx_target)

    # map all "normal" character positions {0:0, 1:1, 2:2, ...}
    mapper = dict((i, i) for i in xrange(len(a)))

    # update with all shuffles in the string: {old_pos:new_pos, old_pos:new_pos, ...}
    mapper.update(zip(idx_todo, idx_target))

    # use mapper to modify the string:
    return ''.join(a[mapper[i]] for i in xrange(len(a)))

for i in xrange(5):
    print partial_shuffle('abcdefghijklmnopqrstuvwxyz', 0.2)

打印

abcdefghljkvmnopqrstuxwiyz
ajcdefghitklmnopqrsbuvwxyz
abcdefhwijklmnopqrsguvtxyz
aecdubghijklmnopqrstwvfxyz
abjdefgcitklmnopqrshuvwxyz
4

这个问题其实比看起来简单。这个编程语言提供了合适的工具,可以让你更容易地实现你的想法,不会让你感到困惑,跟往常一样。

import random

def pashuffle(string, perc=10):
    data = list(string)
    for index, letter in enumerate(data):
        if random.randrange(0, 100) < perc/2:
            new_index = random.randrange(0, len(data))
            data[index], data[new_index] = data[new_index], data[index]
    return "".join(data)
3

你的问题有点棘手,因为需要考虑一些特殊情况:

  • 包含重复字符的字符串(比如说,怎么打乱“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 个字符(如果有重复的字符,那就没办法了)。

用“string”和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'

这个方法的坏处是它不能生成所有可能的变体,比如说,它不能把“string”变成“gnrits”。可以通过将要打乱的索引分成几个部分来解决这个问题,像这样:

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)))

撰写回答