【问题标题】:oneliner scramble programoneliner scramble 程序
【发布时间】:2010-12-16 16:09:29
【问题描述】:

又到了一年中的那个时候,程序员想要重新排列一个列表,使得没有任何元素位于其原始位置(至少在荷兰,我们庆祝 Sinterklaas 并挑选吸管来决定谁写谁一首诗)。有没有人为此提供一个不错的 Python 单一语句

所以,输入例子:range(10)

输出示例:[2,8,4,1,3,7,5,9,6,0]

错误的输出将是[2,8,4,1,3,5,7,9,6,0],因为5 位于其原始位置。这意味着第 5 个人必须为自己写一首诗,这样就没有那么有趣了。

edit 许多人会根据需要重复该任务以走运并发现实际上解决方案是令人满意的。这是一种不好的方法,因为理论上这可能需要无限长的时间。 Bart 确实提出了更好的方法,但出于某种原因,我无法将其纳入单线...

edit 我所说的 oneliner,是指单一语句。看起来,Python 还能够在一行中压缩多个语句。我不知道。目前有非常好的解决方案,仅使用分号来模拟单行上的多行行为。因此:“你能在一个语句中做到吗?”

【问题讨论】:

  • 为什么选择oneliner?为什么不是 5 或 6 行?
  • 因为我可以自己做。容易死。 oneliner 只是寒冷和黑暗的冬天的一个不错的爱好。 :)
  • 我的答案的单一语句版本:exec "for i in range(1,len(L)):import random;r=random.randint(0,i-1);L[i],L[r]=L[r],L[i]"

标签: python code-golf shuffle


【解决方案1】:

我发现 shuffle 可以用来解决这个问题

from random import shuffle
L = ["Anne", "Beth", "Cath", "Dave", "Emma"]
shuffle(L, int=lambda n: int(n - 1))
print L

分布不均匀,但这不是必需的。

#For 100,000 samples

(('Beth', 'Cath', 'Dave', 'Emma', 'Anne'), 13417)
(('Beth', 'Cath', 'Emma', 'Anne', 'Dave'), 6572)
(('Beth', 'Dave', 'Anne', 'Emma', 'Cath'), 3417)
(('Beth', 'Dave', 'Emma', 'Cath', 'Anne'), 6581)
(('Beth', 'Emma', 'Anne', 'Cath', 'Dave'), 3364)
(('Beth', 'Emma', 'Dave', 'Anne', 'Cath'), 6635)
(('Cath', 'Anne', 'Dave', 'Emma', 'Beth'), 1703)
(('Cath', 'Anne', 'Emma', 'Beth', 'Dave'), 1705)
(('Cath', 'Dave', 'Beth', 'Emma', 'Anne'), 6583)
(('Cath', 'Dave', 'Emma', 'Anne', 'Beth'), 3286)
(('Cath', 'Emma', 'Beth', 'Anne', 'Dave'), 3325)
(('Cath', 'Emma', 'Dave', 'Beth', 'Anne'), 3421)
(('Dave', 'Anne', 'Beth', 'Emma', 'Cath'), 1653)
(('Dave', 'Anne', 'Emma', 'Cath', 'Beth'), 1664)
(('Dave', 'Cath', 'Anne', 'Emma', 'Beth'), 3349)
(('Dave', 'Cath', 'Emma', 'Beth', 'Anne'), 6727)
(('Dave', 'Emma', 'Anne', 'Beth', 'Cath'), 3319)
(('Dave', 'Emma', 'Beth', 'Cath', 'Anne'), 3323)
(('Emma', 'Anne', 'Beth', 'Cath', 'Dave'), 1682)
(('Emma', 'Anne', 'Dave', 'Beth', 'Cath'), 1656)
(('Emma', 'Cath', 'Anne', 'Beth', 'Dave'), 3276)
(('Emma', 'Cath', 'Dave', 'Anne', 'Beth'), 6638)
(('Emma', 'Dave', 'Anne', 'Cath', 'Beth'), 3358)
(('Emma', 'Dave', 'Beth', 'Anne', 'Cath'), 3346)

为了均匀分布,可以使用这个(更长的)版本

from random import shuffle,randint
L=["Anne", "Beth", "Cath", "Dave", "Emma"]
shuffle(L, random=lambda: 1, int=lambda n: randint(0, n - 2))
print L

# For 100,000 samples

(('Beth', 'Cath', 'Dave', 'Emma', 'Anne'), 4157)
(('Beth', 'Cath', 'Emma', 'Anne', 'Dave'), 4155)
(('Beth', 'Dave', 'Anne', 'Emma', 'Cath'), 4099)
(('Beth', 'Dave', 'Emma', 'Cath', 'Anne'), 4141)
(('Beth', 'Emma', 'Anne', 'Cath', 'Dave'), 4243)
(('Beth', 'Emma', 'Dave', 'Anne', 'Cath'), 4208)
(('Cath', 'Anne', 'Dave', 'Emma', 'Beth'), 4219)
(('Cath', 'Anne', 'Emma', 'Beth', 'Dave'), 4087)
(('Cath', 'Dave', 'Beth', 'Emma', 'Anne'), 4117)
(('Cath', 'Dave', 'Emma', 'Anne', 'Beth'), 4127)
(('Cath', 'Emma', 'Beth', 'Anne', 'Dave'), 4198)
(('Cath', 'Emma', 'Dave', 'Beth', 'Anne'), 4210)
(('Dave', 'Anne', 'Beth', 'Emma', 'Cath'), 4179)
(('Dave', 'Anne', 'Emma', 'Cath', 'Beth'), 4119)
(('Dave', 'Cath', 'Anne', 'Emma', 'Beth'), 4143)
(('Dave', 'Cath', 'Emma', 'Beth', 'Anne'), 4203)
(('Dave', 'Emma', 'Anne', 'Beth', 'Cath'), 4252)
(('Dave', 'Emma', 'Beth', 'Cath', 'Anne'), 4159)
(('Emma', 'Anne', 'Beth', 'Cath', 'Dave'), 4193)
(('Emma', 'Anne', 'Dave', 'Beth', 'Cath'), 4177)
(('Emma', 'Cath', 'Anne', 'Beth', 'Dave'), 4087)
(('Emma', 'Cath', 'Dave', 'Anne', 'Beth'), 4150)
(('Emma', 'Dave', 'Anne', 'Cath', 'Beth'), 4268)
(('Emma', 'Dave', 'Beth', 'Anne', 'Cath'), 4109)

工作原理

这是random.shuffle()的代码

def shuffle(self, x, random=None, int=int):
    """x, random=random.random -> shuffle list x in place; return None.

    Optional arg random is a 0-argument function returning a random
    float in [0.0, 1.0); by default, the standard random.random.
    """

    if random is None:
        random = self.random
    for i in reversed(xrange(1, len(x))):
        # pick an element in x[:i+1] with which to exchange x[i]
        j = int(random() * (i+1))
        x[i], x[j] = x[j], x[i]

两种解决方案都通过定位j = int(random() * (i+1))这一行来工作

第一个(非统一)有效地使线条像这样工作

j = int(random() * (i + 1) - 1)

所以我们得到的不是 (1..i) 的范围,而是 (0..i-1)

第二种解决方案将random() 替换为始终返回1 的函数,并使用randint 而不是int。所以这条线现在是这样工作的

j = randint(0, i - 1)

【讨论】:

    【解决方案2】:

    洗牌后,让[i]th 人为列表中的[i+1]th 人写一首诗(并买礼物!):那样,永远不会有人画他-或她自己。当然最后一个应该指向第一个...

    【讨论】:

    • 这正是我的多行实现!因为与这里提到的其他方法相比,这保证以线性时间结束。但是在单列式中洗牌和换档是非常困难的。能给个代码吗?
    • 那只会产生“完整的圈子”,不会覆盖像人一和二互相写诗的东西......
    • @sth 细心!看起来 Glex 的 Ruby 代码也一样?
    【解决方案3】:

    以循环方式将列表中的每个元素移动一个,as suggested by Bart,很容易:

    >>> def shift(seq):
    ...     return seq[-1:] + seq[:-1]
    ... 
    >>> shift(range(10))
    [9, 0, 1, 2, 3, 4, 5, 6, 7, 8]
    

    至于随机解决方案:在这种情况下,对单行的请求并不是一个好主意,因为要使用的显而易见的函数,即random.shuffle,就地执行了它的任务.换句话说:它有一个副作用,人们通常会在列表推导中试图避免这种情况。正如Paul 指出的那样,有一种解决方法,即使用random.sample。以下代码显示了两个使用这些函数的单行代码(注意使用not shuffle,以解决shuffle 返回None 的事实...):

    >>> from itertools import repeat
    >>> from random import shuffle
    >>> def shake_it(seq):
    ...     return next(c for c in repeat(seq[::]) if not shuffle(c) and all(a != b for a, b in zip(seq, c)))
    ... 
    >>> shake_it(range(10))
    [7, 9, 0, 2, 6, 8, 5, 1, 4, 3]
    >>> 
    >>> from itertools import count
    >>> from random import sample
    >>> def shake_it(seq):
    ...     return next(c for c in (sample(seq, len(seq)) for _ in count()) if all(a != b for a, b in zip(seq, c)))
    ... 
    >>> shake_it(range(10))
    [1, 3, 9, 5, 2, 6, 8, 4, 0, 7]
    

    我自己,我会选择这个:

    >>> def shake_it(seq):
    ...     res = seq[::]
    ...     while any(a == b for a, b in zip(res, seq)):
    ...         shuffle(res)
    ...     return res
    ... 
    >>> shake_it(range(10))
    [5, 7, 9, 2, 6, 8, 3, 0, 4, 1]
    

    【讨论】:

    • 如果你愿意,用random.shuffle(x) 代替random.sample(x, len(x)) 以获得返回值。
    • @Paul:好点子!我已经用一个使用shuffle 的单行代码更新了答案。
    • 我认为这种oneliner 的思维方式与Wim 的非常相似,但他的 更易于阅读。 repeat 的使用非常有前途。我不知道这个功能,我敢打赌它会成为最终解决方案的一部分。
    • 除非我弄错了,否则 Wim 的解决方案依赖于输入为 range(n) 的假设(即从零开始的连续数字列表)。我的代码没有做这样的假设,因此更通用。 (即,我可以随机排列实际荷兰人姓名的列表;)
    【解决方案4】:

    以下是使用 O(n) 时间和 O(1) 额外内存的方法:

    可理解的代码:

    def shuffle(a)
      n = a.length
      (0..n - 2).each do |i|
        r = rand(n - i - 1) + i + 1
        a[r], a[i] = a[i], a[r]
      end
      a
    end
    

    单行(假设“a”是数组):

    n = a.length and (0..n - 2).each {|i| r = rand(n - i - 1) + i + 1; a[r], a[i] = a[i], a[r]}
    

    代码是 ruby​​ 的,但毫无疑问它很容易翻译成 python

    干杯

    P.S.:解决方案修改了数组。

    【讨论】:

      【解决方案5】:

      固定 O(n) 时间内的“单线”:

      import random; a=range(10)  # setup (could read in names instead)
      for i in range(len(a)-1,0,-1): j=random.randint(0,i-1); a[j],a[i]=a[i],a[j]
      print a  # output
      

      循环从最大索引 (len(a)-1) 到下一个最小索引 (1) 选择元素。元素 k 的选择池仅包含从 0 到 k-1 的索引;一旦被选中,元素将不会再次移动。

      打乱后,没有元素可以留在原来的位置,因为:

      • 如果为某个槽 i>j 选择元素 j,它将保留在那里
      • 否则,元素 j 将与插槽 i
      • 除了槽 0 中的元素,如果它尚未被替换,它将无条件地与槽 1 中的元素交换(在循环的最后一次迭代中)。

      [编辑:我认为这在逻辑上等同于 Ruby 答案]

      【讨论】:

      • 太棒了!是的,它就像红宝石解决方案,我喜欢算法,因为它很简单。你能写在一个语句中吗?
      • 嗯,你可以“;eval'...'”中间线,但那会很伤心。
      【解决方案6】:

      这个是 O(N)。在循环中导入有点傻,但你想要一个单行

      L=range(10)
      for i in range(1,len(L)):import random;r=random.randint(0,i-1);L[i],L[r]=L[r],L[i]
      print L
      

      这是 100000 个样本 L=range(5) 时的输出分布

      ((1, 2, 3, 4, 0), 4231)
      ((1, 2, 4, 0, 3), 4115)
      ((1, 3, 0, 4, 2), 4151)
      ((1, 3, 4, 2, 0), 4108)
      ((1, 4, 0, 2, 3), 4254)
      ((1, 4, 3, 0, 2), 4101)
      ((2, 0, 3, 4, 1), 4158)
      ((2, 0, 4, 1, 3), 4177)
      ((2, 3, 1, 4, 0), 4190)
      ((2, 3, 4, 0, 1), 4117)
      ((2, 4, 1, 0, 3), 4194)
      ((2, 4, 3, 1, 0), 4205)
      ((3, 0, 1, 4, 2), 4325)
      ((3, 0, 4, 2, 1), 4109)
      ((3, 2, 0, 4, 1), 4131)
      ((3, 2, 4, 1, 0), 4153)
      ((3, 4, 0, 1, 2), 4081)
      ((3, 4, 1, 2, 0), 4118)
      ((4, 0, 1, 2, 3), 4294)
      ((4, 0, 3, 1, 2), 4167)
      ((4, 2, 0, 1, 3), 4220)
      ((4, 2, 3, 0, 1), 4179)
      ((4, 3, 0, 2, 1), 4090)
      ((4, 3, 1, 0, 2), 4132)
      

      【讨论】:

      • 当然,不要考虑导入。此外,我将编辑我的问题,因为我发现自己在搜索单个语句,而不是“实际上包含多个语句的单行”。
      • 我发布了另一个答案,但我也把这个留在这里,因为我认为这是一个很好的多语句解决方案
      【解决方案7】:

      很久以来我的第一个 Python 程序。与上述许多程序不同,这个程序需要 O(n) 时间。

      s = set(range(10))
      r = list()
      for i in range(10):
          s2 = s - set([i])
          val = s2.pop()
          r.append(val)
          s.discard(val)
      
      print r
      

      更新:Paul 表明上述程序不正确。谢谢,保罗。这是同一程序的不同更好版本:

      s = range(10)
      for i in range(9):
          r = random.randrange(i+1, 10)
          s[i], s[r] = s[r], s[i]
      
      print s
      

      【讨论】:

      • 你的意思是这样的:reduce(lambda a,i:a+[random.choice(list(set(x)-set(a+[i])))], x, [])?当最后一个剩余的项目被强制引用自身时,它会抛出一个 IndexError。
      • Paul -- 如果它抛出一个 IndexError,那么我的代码重写不是很好。
      • 好吧,我只是不明白你的代码。现在我运行您的代码,它总是给出相同的结果。对于 range(10),r 始终为 [1, 0, 3, 2, 8, 9, 4, 5, 6, 7]。所以不是真的随机。
      • 是的; pop() 从列表中返回任意元素,而不是随机元素。如果我使用列表而不是集合,我的答案会更容易理解吗?
      • 假设实际上集合上有一个.poprandom(),那么当 s 中的最后一个剩余项是列表中的最后一个元素时(9 在 range 的示例中),您的算法将抛出 IndexError (10))。
      【解决方案8】:

      抱歉,这不是单行的,但它有效

      import random
      def sinterklaas(n):
          l=[]
          for a in range(n):
              l.append(-1)
      
          i = 0
          while i < 10:
              index = random.randint(0,n-1)
              if l[index] == -1 and index != i:
              l[index] = i
                  i += 1
      

      干杯

      【讨论】:

      • 这个:l=[]; for a in range(n): l.append(-1) 真的是这个:l = [-1] * n
      • 感谢您提供这么棒的信息。我以前不知道这一点。
      【解决方案9】:
      import random; u = range(10)
      while sum(u[i]==i for i in range(10)): random.shuffle(u)
      

      (好吧,我也有第 0 行……)

      【讨论】:

      • 我认为你的意思是随机导入,而不是范围!
      • 呃,需要 5 次编辑才能让 oneliner 正常工作...必须...得到...咖啡...!!
      • 干得好!虽然它需要过多的计算(并且理论上不能保证完成)
      • 哦,理论上保证完结。不能保证在任何特定 时间 内完成,但我认为预期的迭代次数是渐近常数。
      • 不,不能保证完成。如果您的随机数生成器只能生成与 don't write needle to self 规则不匹配的排列,您可以尝试 very 很长时间...(我知道,这将是一个非常可悲的随机生成器,但我们说的是理论上的,对吧?)
      【解决方案10】:

      对于 O(n) 中的一个:

      u=range(10); random.shuffle(u); v=[ u[u[i]] for i in range(10) ]; return [ v[(u[i]+1)%10] for i in u ]
      

      u 是函数v 的逆函数,因此v[u[i]+1] 实际上是数组v 中i 之后的元素。

      【讨论】:

      • 我认为这是一个好的方向。你也可以不用分号吗?如果您愿意,您也可以像这样随机播放返回值:shuffled = random.sample(x, len(x))
      • 我试过运行你的程序。我的回报是:[0, 1, 5, 2, 6, 9, 3, 4, 7, 8]。心疼第一二个人!
      【解决方案11】:

      这是 Stephan202 的循环移位,它实现为单线,具有随机选择的移位增量:

      from random import randrange; s = range(10); r = randrange(1,len(s)-1); print s[-r:] + s[:-r]
      

      【讨论】:

      • 是的,但不是洗牌。
      猜你喜欢
      • 1970-01-01
      • 2010-12-30
      • 2018-07-10
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多