15

假设我有两个数组:

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

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

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

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

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

另请注意,“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
4

15 回答 15

16

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

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

或者更有效:

c = map(next, random.sample([iter(a)]*len(a) + [iter(b)]*len(b), len(a)+len(b)))

请注意,上面的第一种方法会修改原始列表(正如您的代码所做的那样),而第二种方法则不会。在 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

这是另一种选择,但只有在您知道所有元素都不是假的(没有0''NoneFalse或空序列)时才有效:

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))]
于 2012-05-17T23:57:36.123 回答
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)

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

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

运行这个

a = [1,2,3,4]
b = [5,6,7,8,9]
print interleave(a, b)

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

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

编辑:应 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

这是我的函数版本的时间

$ 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

以下是 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

如果我从 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

另一个编辑: 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
于 2012-05-18T00:27:30.887 回答
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))]

该解决方案避免了明确测试是否为空的特定a情况b

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

  • 使用randrange()允许人们简单地处理整数(无需计算比率)。
  • 它会自动适应空列表(即< len(a)测试),而无需任何额外的测试,例如a or b[… a and b]+a+b...</li>

这个解决方案可以很好地处理不同大小的列表:较短列表的元素在结果中分布得非常均匀。这种方法还具有“不变性”的特点:可能结果列表的概率分布仅取决于和列表的当前内容。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))]
于 2012-05-18T08:28:45.930 回答
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
于 2012-05-18T00:41:05.117 回答
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)))
于 2012-05-18T07:46:51.377 回答
2

连接,然后打乱一个标志数组,然后用它来选择一个数组来获取每个项目怎么样?

import random

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

c = list('a' * len(a) + 'b' * len(b)) # Flags for taking items from each array
random.shuffle(c) # Randomize from where we take items

aa, bb = a[:], b[:] # Copy the arrays for popping 
d = [aa.pop(0) if source == 'a' else bb.pop(0) for source in c]
# Take items in order, randomly from each array

FogleBird 的一种更有效的方法:

c = [a[:]] * len(a) + [b[:]] * len(b)
random.shuffle(c) # Randomize from where we take items

d = [x.pop(0) for x in c] # Take items in order, randomly from each array
于 2012-05-18T00:03:38.750 回答
1

该解决方案为您提供了一个生成器,并通过随机交换列表 (a) 和 (b) 尚未发出的部分来工作。

import random

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

def interleave(a,b):
   while a or b:
      (a,b)=(a,b) if len(a) and (random.random()<0.5 or not len(b)) else (b,a)
      yield a.pop(0)

print list(interleave(a,b))
于 2012-05-18T00:11:52.680 回答
1

这是使用未记录的 Python(特别__length_hint__是列表迭代器对象的方法,它告诉您在迭代器中剩余多少项)将其塞入列表推导式的东西。我想,更多的是为了好玩,而不是实际的实用性。

itera, iterb = iter(a), iter(b)
morea, moreb = itera.__length_hint__, iterb.__length_hint__
c = [next(itera) if not moreb() or morea() and random.random() < ratio
     else next(iterb) for c in xrange(len(a) + len(b))]
于 2012-05-18T00:18:37.953 回答
1

我会这样解决这个问题:

import random

LResult = []

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

while LLists[0] or LLists[1]:
    LResult.append(LLists[random.choice([int(len(LLists[0])==0), int(len(LLists[1])!=0)])].pop(0))

LLists 是一个多维列表,它存储两个列表(您的示例中的 a 和 b)。该语句等效于: LLists = [a[:], b[:]] 但是为了简单明了,我在列表中明确编码。

LResult 是您示例中的 c 并最终存储结果数组。

while 循环将循环直到 LLists sub 0 和 LLists sub 1 都完全为空。在循环中,LResult 被附加一个来自 LLists sub 0 或 LLists sub 1 的值。关于选择哪个子列表的值的决定由 random.choice() 语句确定,该语句接受两个(在这种情况下)参数,然后随机返回其中之一。

提供给 random.choice() 的选项由 LLists 中每个子列表的长度决定。如果 LLists sub 0 的长度大于零,则语句 int(len(LLists[0])==0) 将选项号 1 作为零返回。对于 random.choice() 的第二个选项,如果 LLists sub 1 的长度大于零,则语句 int(len(LLists[1])!=0) 将返回 1。在这两种情况下,如果一个子列表的长度为零,则相应的语句将返回相反的数字。也就是说,如果 LLists[0] 的长度为零,并且 LLists[1] 的长度大于零,则生成的语句将是 random.choice(1, 1)。在这种情况下 random.choice() 将返回 1 和 1 之间的选择(当然是 1)。

一旦决定从哪个子列表中提取值,第一项就是将子列表弹出(.pop(0))到 LResult 中。

于 2012-05-26T17:45:17.993 回答
0

你可以这样做:

(L, l) = (a, b) if len(a) > len(b) else( b, a)
positions = random.sample(range(len(L)), len(l))
for i in range(len(positions)):
    L.insert(positions[i], l[i])

但以我的拙见,你所拥有的一切都很好..它有效,很简单

于 2012-05-18T00:06:24.060 回答
0

这个想法怎么样:

import random as rn

a = [1, 2, 3, 4]
b = [5, 6, 7, 8, 9]
n = 100 #Here i am picking an arbitrary number, it should probably be a function of 
        # lengths of a and b


a_ind = sorted(rn.sample(range(n),len(a))) #sorting the indexes insures that order of 
b_ind = sorted(rn.sample(range(n),len(b))) # a and b is preserved

big_list = zip(a,a_ind) + zip(b,b_ind)

big_list.sort(key  = lambda k: k[1])

result = list(zip(*big_list)[0])

结果:

>>> result
[1, 5, 2, 6, 3, 7, 8, 9, 4]
于 2012-05-18T00:35:50.887 回答
0

描述中的“交错”一词可能令人困惑。如果您只是添加输入列表,然后将结果打乱,您会得到相同的结果。仅当要保留交织的结果时才需要交织。

一些代码:

>>> import random
>>> 
>>> a, b = [1,2,3,4], [5,6,7,8]
>>> c = sum([a,b], [])
>>> random.shuffle(c)
>>> c
[6, 5, 8, 2, 7, 4, 1, 3] 
于 2012-05-21T15:17:42.887 回答
0

可能非常低效,但另一种方法有效:

import random

def interleave(*args):
    indices=[(i,j) for i in range(len(args)) for j in range(len(args[i]))]
    random.shuffle(indices)
    indices.sort(key=lambda x:x[1])
    return [args[i][j] for i,j in indices]
于 2013-10-23T13:43:37.503 回答
0

我已经调整了@NPE 的解决方案,因此它以恒定而不是线性时间*删除空迭代器。它接受任意数量的输入列表,并返回一个随机交错它们的迭代器,保留输入列表给定的顺序。

def interleave(*args):
  iters = [iter(x) for x in args]
  while iters:
    i = random.randrange(len(iters))
    try:
      yield next(iters[i])
    except StopIteration:
      # swap empty iterator to end and remove
      iters[i],iters[-1] = iters[-1],iters[i]
      iters.pop()

print list(interleave(xrange(1, 5), xrange(5, 10), xrange(10, 15)))

*总运行时间而O(N)不是项目总数和列表数。O(N+M^2)NM

于 2016-11-30T18:52:43.687 回答
0

如果列表 1 和列表 2 之间的比率保持不变,您可以创建如下函数:

def selectFromTwoList(ratioFromList1):
    final_list = []
    for i in range(len(list1)):
        rand = random.randint(1, 100)
        if rand <= ratioFromList1*100:
            final_list.append(list1.pop(0))
        else:
            final_list.append(list2.pop(0))
        return final_list
于 2018-08-22T19:43:28.340 回答