【问题标题】:How to make a random but partial shuffle in Python?如何在 Python 中进行随机但部分洗牌?
【发布时间】:2012-01-10 13:16:26
【问题描述】:

我在 python 中寻找 partial shuffle 函数,而不是完整的 shuffle

示例:“字符串”必须产生“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)

【问题讨论】:

  • 洗牌代码的问题是,如果有一系列交换产生循环,那么你最终得到的洗牌字符可能比预期的要少......
  • 是的,小字符串很有可能。我认为我的代码偏向于速度而不是准确性。
  • 其他一些提示:为什么在循环结束时增加i?它不应该有任何影响(我认为这是while 版本的遗留物);使用元组解构而不是使用中间变量,交换在 Python 中更惯用。
  • 是的,i=i+1 是不必要的。您能否详细说明“元组解构”?
  • 当然,就像a, b = b, a 一样简单,例如你可以在@jsbueno 的答案中看到它...顺便说一句,确切的名称是“元组解包”docs.python.org/tutorial/…

标签: python string random shuffle


【解决方案1】:

这是一个比看起来更简单的问题。而且语言有正确的工具,不会像往常一样阻碍你和想法:

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)

【讨论】:

  • 哇,您的代码是如此可重用和干净,但它并不能解决任务。
  • 它怎么能用perc=50 打乱整个字符串?
  • 还是这样?那么放在那里的“/2”呢?
  • 任务是随机播放字符串的某个百分比,而不是按照给定的概率。不过好吧,你解决了任务,你的代码很干净,这里不用争论了。
【解决方案2】:

您的问题很棘手,因为需要考虑一些边缘情况:

  • 具有重复字符的字符串(即,您将如何随机播放“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位置进行交换,然后将它们与列表中的下一个交换...是重复的字符,运气不好)。

以 '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'

这种方法的坏处是它不会生成所有可能的变体,例如,它不能从“字符串”生成“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)))

【讨论】:

    【解决方案3】:
    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】:

      邪恶并使用已弃用的 API:

      import random
      # adjust constant to taste
      # 0 -> no effect, 0.5 -> completely shuffled, 1.0 -> reversed
      # Of course this assumes your input is already sorted ;)
      ''.join(sorted(
          'abcdefghijklmnopqrstuvwxyz',
          cmp = lambda a, b: cmp(a, b) * (-1 if random.random() < 0.2 else 1)
      ))
      

      【讨论】:

      • 很有趣,但它保证总是完成吗?如果比较函数不一致,我认为可能有一系列排序步骤可能永远循环(取决于所使用的算法,例如快速排序应该对此免疫)。
      【解决方案5】:

      可能是这样的:

      >>> s = 'string'
      >>> shufflethis = list(s[2:])
      >>> random.shuffle(shufflethis)
      >>> s[:2]+''.join(shufflethis)
      'stingr'
      

      从 fortran 的想法中,我将其添加到集合中。速度非常快:

      def partial_shuffle(st, p=20):
          p = int(round(p/100.0*len(st)))
      
          idx = range(len(s))
          sample = random.sample(idx, p)
      
          res=str()
          samptrav = 1
      
          for i in range(len(st)):
              if i in sample:
                  res += st[sample[-samptrav]]
                  samptrav += 1
                  continue
              res += st[i]
      
          return res
      

      【讨论】:

      • 这将每次都打乱字符串的相同部分。
      猜你喜欢
      • 2019-09-20
      • 1970-01-01
      • 1970-01-01
      • 2014-01-06
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多