oneliner争夺计划

时间:2009-11-14 20:52:28

标签: python code-golf shuffle

这是一年中的那个时候,程序员想要洗牌,以便没有任何元素存在于其原始位置(至少在荷兰,我们庆祝 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个人必须为自己写一首诗,这样就不那么有趣了。

编辑许多人只要需要获得幸运就重复这项任务,并发现事实上解决方案是令人满意的。这是一种糟糕的方法,因为理论上这可能需要很长时间。巴特确实提出了更好的方法,但由于某种原因我不能把它变成一个oneliner ......

编辑通过oneliner,我的意思是单一陈述。看起来,Python也能够在一行上压缩多个语句。我不知道。目前有非常好的解决方案,只使用分号来模拟单行上的多线行为。因此:“你能在一个声明中做到吗?”

11 个答案:

答案 0 :(得分:6)

我发现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)

因此,我们获得(0..i-1)

而不是(1..i)的范围

第二个解决方案使用始终返回1的函数替换random(),并使用randint代替int。所以这条线现在就像这样

j = randint(0, i - 1)

答案 1 :(得分:5)

在对数字列表进行洗牌之后,让[i]人为列表中的[i+1]人写一首诗(并购买礼物!):这样就永远不会有人谁吸引他或她自己。当然,最后一个应该指向第一个......

答案 2 :(得分: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]

答案 3 :(得分:1)

我的第一个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

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

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

print s

答案 4 :(得分:1)

以下是使用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 :(得分:1)

固定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的索引;一旦被挑选,元素将不会再被移动。

在争夺之后,没有任何元素可以驻留在其原始位置,因为:

  • 如果选择元素j用于某个插槽i> j,它将保留在那里
  • 否则,元素j将与插槽i< j中的其他元素交换,它将保留在那里
  • 除了插槽0中的元素之外,如果它还没有被替换,它将无条件地与插槽1中的元素交换(在循环的最后一次迭代中)。

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

答案 6 :(得分:1)

这个是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

这是当L = 100000个样本的范围(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 :(得分:0)

对不起,这不是单行,但这有效

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

干杯

答案 8 :(得分:0)

import random; u = range(10)
while sum(u[i]==i for i in range(10)): random.shuffle(u)

(好吧,我也有一条0号线......)

答案 9 :(得分:0)

对于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后面的元素。

答案 10 :(得分:0)

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

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