在Python中随机交错2个数组

sal*_*lil 14 python arrays random

假设我有两个数组:

a = [1, 2, 3, 4]
b = [5, 6, 7, 8, 9]
Run Code Online (Sandbox Code Playgroud)

我想将这两个数组交织到变量'c'(注意'a'和'b'不一定长度相等)但我不希望它们以确定的方式交错.简而言之,仅仅压缩这两个数组是不够的.我不想要:

c = [1, 5, 2, 6, 3, 7, 4, 8, 9]
Run Code Online (Sandbox Code Playgroud)

相反,我想要一些随机的东西:

c = [5, 6, 1, 7, 2, 3, 8, 4, 9]
Run Code Online (Sandbox Code Playgroud)

另请注意,'a'和'b'的顺序保留在结果数组'c'中.

我目前的解决方案需要一个for循环和一些随机数生成.我不喜欢它,我希望有人能指出我更好的解决方案.

# resulting array
c = []

# this tells us the ratio of elements to place in c. if there are more elements 
# in 'a' this ratio will be larger and as we iterate over elements, we will place
# more elements from 'a' into 'c'.
ratio = float(len(a)) / float(len(a) + len(b))

while a and b:
    which_list = random.random()
    if which_list < ratio:
        c.append(a.pop(0))
    else:
        c.append(b.pop(0))

# tack on any extra elements to the end
if a:
    c += a
elif b:
    c += b
Run Code Online (Sandbox Code Playgroud)

And*_*ark 15

编辑:我认为最近这个是最好的:

a = [1, 2, 3, 4]
b = [5, 6, 7, 8, 9]
c = [x.pop(0) for x in random.sample([a]*len(a) + [b]*len(b), len(a)+len(b))]
Run Code Online (Sandbox Code Playgroud)

或者更有效率:

c = map(next, random.sample([iter(a)]*len(a) + [iter(b)]*len(b), len(a)+len(b)))
Run Code Online (Sandbox Code Playgroud)

请注意,上面的第一种方法修改了原始列表(如代码所做的那样),而第二种方法则没有.在Python的3.x的,你需要做的list(map(...)),因为map返回迭代器.

原答案如下:

这是一个节省几行的选项:

a = [1, 2, 3, 4]
b = [5, 6, 7, 8, 9]

c = []
tmp = [a]*len(a) + [b]*len(b)
while a and b:
    c.append(random.choice(tmp).pop(0))

c += a + b
Run Code Online (Sandbox Code Playgroud)

这是另一种选择,但如果你知道你所有的元素都没有falsy它只会工作(不0,'',None,False,或空序列):

a = [1, 2, 3, 4]
b = [5, 6, 7, 8, 9]

ratio = float(len(a)) / float(len(a) + len(b))
c = [(not a and b.pop(0)) or (not b and a.pop(0)) or
     (random.random() < ratio and b.pop(0)) or a.pop(0)
     for _ in range(len(a) + len(b))]
Run Code Online (Sandbox Code Playgroud)

  • 这太过分了.您不需要创建这么多额外的列表.您可以从2个迭代中进行选择 (3认同)
  • @TryPyPy:产生不同的结果 - 你的a和b之间的概率总是50/50. (2认同)
  • @FJ:对.然后,我会把`shuffle()`放在列表解析之外,这样代码(1)运行得更快,(2)更明确(当前解决方案中的`sample()`真的是一个shuffle).我很高兴看到你的解决方案已经融合到srgerg的原始解决方案(使用迭代器和`next()`).:) (2认同)

srg*_*erg 9

编辑删除多余的混乱:这是一个适用于任意数量的输入列表的解决方案,不会丢弃输入列表,也不会复制它们:

import random

def interleave(*args):
    iters = [i for i, b in ((iter(a), a) for a in args) for _ in xrange(len(b))]
    random.shuffle(iters)
    return map(next, iters)
Run Code Online (Sandbox Code Playgroud)

Stackoverflow用户EOL已经提供了我的解决方案的增强版本:

def interleave(*args):
    iters = sum(([iter(arg)]*len(arg) for arg in args), [])
    random.shuffle(iters)
    return map(next, iters)
Run Code Online (Sandbox Code Playgroud)

运行这个

a = [1,2,3,4]
b = [5,6,7,8,9]
print interleave(a, b)
Run Code Online (Sandbox Code Playgroud)

产生以下结果作为许多可能的结果之一:

[5, 6, 7, 1, 8, 2, 3, 9, 4]
Run Code Online (Sandbox Code Playgroud)

编辑:根据EOL的要求,我更新了时间码.不幸的是,由于接受的解决方案修改了它的输入,我需要在每次迭代时制作一个新的副本.我已经为FJ和我自己的解决方案做了这个,以使结果可比.这是F.Js解决方案的时机:

$ python -m timeit -v -s "from srgerg import accepted" -s "a = list(xrange(40000))" -s "b = list(xrange(60000))" "accepted(list(a), list(b))"
10 loops -> 10.5 secs
raw times: 10.3 10.1 9.94
10 loops, best of 3: 994 msec per loop
Run Code Online (Sandbox Code Playgroud)

这是我的功能版本的时间

$ python -m timeit -v -s "from srgerg import original" -s "a = list(xrange(40000))" -s "b = list(xrange(60000))" "original(list(a), list(b))"
10 loops -> 0.616 secs
raw times: 0.647 0.614 0.641
10 loops, best of 3: 61.4 msec per loop
Run Code Online (Sandbox Code Playgroud)

这是EOL增强版的时机:

$ python -m timeit -v -s "from srgerg import eol_enhanced" -s "a = list(xrange(40000))" -s "b = list(xrange(60000))" "eol_enhanced(list(a), list(b))"
10 loops -> 0.572 secs
raw times: 0.576 0.572 0.588
10 loops, best of 3: 57.2 msec per loop
Run Code Online (Sandbox Code Playgroud)

如果我从EOL的增强版本的循环中删除列表复制,我得到这个:

$ python -m timeit -v -s "from srgerg import eol_enhanced" -s "a = list(xrange(40000))" -s "b = list(xrange(60000))" "eol_enhanced(a, b)"
10 loops -> 0.573 secs
raw times: 0.572 0.575 0.565
10 loops, best of 3: 56.5 msec per loop
Run Code Online (Sandbox Code Playgroud)

另一个编辑: FJ有一个更新的解决方案,并要求我添加时间:

$ python -m timeit -v -s "from srgerg import fj_updated" -s "a = list(xrange(40000))" -s "b = list(xrange(60000))" "fj_updated(list(a), list(b))"
10 loops -> 0.647 secs
raw times: 0.652 0.653 0.649
10 loops, best of 3: 64.9 msec per loop
Run Code Online (Sandbox Code Playgroud)


Eri*_*got 7

PS:请考虑阅读@ srgerg的答案:在我看来,这是最好的解决方案(尽管FJ相对接近).与下面的解决方案相比,它更通用,甚至更直接,而且只需要大约两倍的内存.

这是既简单有效的东西:

[(a if random.randrange(0, len(a)+len(b)) < len(a) else b).pop(0) for _ in range(len(a)+len(b))]
Run Code Online (Sandbox Code Playgroud)

该解决方案避免了测试为明确是否具体情况a或者b是空的.

该解决方案使用了几个关键点:

  • 使用randrange()允许一个人简单地处理整数(不需要计算比率).
  • 它可以自动适应是空的(这是列出了< len(a)测试),而不需要像其他测试a or b,[… a and b]+a+b...

这个解决方案可以很好地处理不同大小的列表:较短列表的元素在结果中非常均匀地分布.该方法还具有"不变性":可能结果列表的概率分布仅取决于和列表的当前内容.ab

通过使用更快.pop()而不是.pop(0)(因为列表是快速pop()但不是pop(0)),可以使它更有效:

a.reverse(); b.reverse()
[(a if random.randrange(0, len(a)+len(b)) < len(a) else b).pop() for _ in range(len(a)+len(b))]
Run Code Online (Sandbox Code Playgroud)


Joe*_*ett 6

编辑TryPyPy的建议:

from random import choice

l = [a, b]
c = [choice(l).pop(0) for i in range(len(a) + len(b)) if (a and b)] + a + b
Run Code Online (Sandbox Code Playgroud)

  • 有趣而且非常简单的解决方案.但是,该解决方案的一个"特征"是,如果其中一个列表比另一个列表长得多,那么短列表可能会很快耗尽.可能希望将较短的元素更均匀地分布在结果列表中. (2认同)

NPE*_*NPE 6

这是一个适用于任意数量的迭代的解决方案:

import random

def interleave(*args):
  iters = map(iter, args)
  while iters:
    it = random.choice(iters)
    try:
      yield next(it)
    except StopIteration:
      iters.remove(it)

print list(interleave(xrange(1, 5), xrange(5, 10), xrange(10, 15)))
Run Code Online (Sandbox Code Playgroud)

  • +1很好的答案:这有[理想的属性](http://stackoverflow.com/q/10648331/631586)采取任意数量的参数,不修改其输入,不采取其输入的副本,不要求其输入支持`len()`方法.唯一的缺点是它比[我的解决方案](http://stackoverflow.com/a/10645233/631586)慢大约1/3 - 但如果没有try-except,它可能会更快. (2认同)