在 Python 中随机交错 2 个数组
Posted
技术标签:
【中文标题】在 Python 中随机交错 2 个数组【英文标题】:Randomly Interleave 2 Arrays In Python 【发布时间】:2012-05-25 13:45:25 【问题描述】:假设我有两个数组:
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
【问题讨论】:
但是您只想随机化交错或整个数组?我的意思是,您需要保留原始数组顺序吗? 你不喜欢它什么?您将需要生成随机数,而您可以用列表推导替换许多循环有什么意义? 是的,这对我来说似乎很好。我相信你可以写一些更紧凑但“简单胜于复杂”的东西。 随机交错列表是什么意思?例如,[1, 2, 3, 4, 5, 6, 7, 8, 9]
是随机交织的有效结果。您想随机化插入之间的时间段吗?
除了有趣的 Python,你真的需要考虑下一步应该做什么。如果您已经选择了 1、2、3,下一个选择应该是 50% 4/50% 5(甚至按列表)还是 16% 4、84% 5(加权 1 个未选择项目列表 a 与 5 个未选择项目列表b).
【参考方案1】:
如果列表 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
【讨论】:
【参考方案2】:我已经修改了@NPE's solution,因此它会以恒定而不是线性时间*删除空迭代器。它接受任意数量的输入列表,并返回一个随机交错的迭代器,保留输入列表给定的顺序。
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)
,其中N
是项目总数,M
是列表数。
【讨论】:
【参考方案3】:可能非常低效,但另一种可行的方法:
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]
【讨论】:
【参考方案4】:我会这样解决这个问题:
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 中。
【讨论】:
【参考方案5】:已编辑以消除多余的混乱:这是一个适用于任意数量的输入列表的解决方案,不会破坏输入列表,也不会复制它们:
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)
*** 用户 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 的要求,我更新了计时码。不幸的是,由于接受的解决方案修改了它的输入,我需要在每次迭代时制作一个新的副本。我已经为 F.J 和我自己的解决方案做了这个,以使结果具有可比性。以下是 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
另一个编辑: F.J 有一个更新的解决方案,并要求我添加时间:
$ 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
【讨论】:
我为你的interleave()
函数添加了一个更简单的版本。
+1:我相信这应该是公认的答案:它比我的解决方案更快,而且更通用、更优雅(尤其是我的简化版interleave()
),而且只需要只有大约两倍的内存。 :)
您的计时应该以不同的方式完成:您应该使用a
和b
的设置初始化 计时器,并且只为函数调用计时。事实上,列表创建可能占用了总时间的很大一部分。我还相信,如果您使用更长的示例列表(例如 100000 个数字),时间差异会更加显着。
@EOL 此外,一些解决方案修改其输入的事实意味着我必须在每次迭代时制作输入的新副本......
非常好的计时结果。请注意,list(xrange(40000))
真的应该写成range(40000)
,就像往常一样(事实上,如果有人在之后立即构建一个列表,那么使用 xrange 是没有意义的)。【参考方案6】:
编辑:我认为最近的这个最好:
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
这是另一种选择,但它只有在您知道所有元素都不是 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))]
【讨论】:
这太过分了。您不需要创建这么多额外的列表。您可以从 2 个迭代器中进行选择 我没有创建额外的列表,tmp
仅包含对 a
和 b
的引用。
我可以看到 3 个已创建但不需要存在的列表。您的第二个解决方案似乎更好,但可读性不太好(并且您更改了原始列表)。
@TryPyPy:这会产生不同的结果——你的结果总是 a 和 b 之间的 50/50 概率。
@F.J:对。然后,我会将 shuffle()
排除在列表理解之外,以便代码 (1) 运行得更快并且 (2) 更明确(当前解决方案中的 sample()
确实进行了洗牌)。我很高兴看到您的解决方案已经融合到 srgerg 的原始解决方案(使用迭代器和 next()
)。 :)【参考方案7】:
PS:请考虑阅读@srgerg's answer:在我看来,这是最好的解决方案(尽管 F.J's 相对接近)。与下面的方案相比,它更通用,甚至更简单一点,而且只占用大约两倍的内存。
这里有一个既简单又高效的东西:
[(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
这样的额外测试......
这个解决方案可以很好地处理不同大小的列表:较短列表的元素在结果中分布得非常均匀。这种方法还具有“不变性”:可能结果列表的概率分布仅取决于a
和b
列表的当前 内容。
使用更快的.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))]
【讨论】:
也许我误解了一些东西,但你说解决方案“避免测试 a 或 b 的长度”但调用len(a)
和 len(b)
显然在解决方案中?
@srgerg:很好:我的意思是“避免附加测试”(编辑后)。事实上,许多解决方案都添加了非零列表长度测试,例如while a and b
或not a or
等。实际上不需要对异常情况(即零长度列表)进行特定处理。
不错。我喜欢使用 randrange。
+1 表示将较短的列表平均分布在较长的列表中的解决方案。 *** 用户 Mark Byers 已将 some comments 写为 have I this other question。【参考方案8】:
描述中的“交错”一词可能令人困惑。如果您只是添加输入列表,然后将结果打乱,您会得到相同的结果。只有在要保留交织结果时才需要交织。
一些代码:
>>> 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]
【讨论】:
顺序很重要(我对这个确切的回答有完全相同的答案)。【参考方案9】:这是一个适用于任意数量的迭代的解决方案:
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)))
【讨论】:
+1 不错的答案:desirable properties 可以接受任意数量的参数,不修改其输入,不复制其输入,也不要求其输入支持len()
方法。唯一的缺点是它比my solution 慢了大约 1/3 - 但如果没有 try-except 可能会更快。
*** 用户 Mark Byers 对这种方法的随机性做了一些worthwhile comments 来回答another question。我用interesting results做了一些我自己的测试。
非常好的解决方案。我已经提交了一个修改版本,当有大量迭代器时,它可以加快删除空迭代器的速度:***.com/a/40896022/431087【参考方案10】:
这是使用未记录的 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))]
【讨论】:
【参考方案11】:如何连接,然后打乱一个标志数组,然后使用它来选择一个数组来获取每个项目?
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
【讨论】:
既然可以使用对列表的引用,为什么还要使用标志? 我可以说比大多数其他答案更容易掌握洗牌和拿东西,但事实是我从误读问题开始洗牌。你能提出一种方法来保持它与改组标志一样明显但使用对列表的直接引用吗? 美丽,我从没想过:)【参考方案12】:根据 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
【讨论】:
choice + pop 真的很好看,通俗易懂。怎么样:[choice(l).pop(0) for i in range(len(a+b)) if (a and b) ] + a + b
有趣且相当简单的解决方案。然而,这个解决方案的一个“特点”是,如果其中一个列表比另一个长得多,那么短列表可能很快就会用完。可能需要让较短的元素更均匀地分布在结果列表中。
@EOL:你是对的。上述方法只适用于大小大致相同的列表。
为了解决这个问题,可以调整a
和b
在l
中的引用比率。例如,如果a
是b
的两倍,那么l = [a, b, b]
。我想知道实现这一点的有效方法是什么......
@JoelCornett:我的回答帖子提供了一种有效的方法来实现这一点。 :)【参考方案13】:
这个想法怎么样:
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]
【讨论】:
【参考方案14】:此解决方案为您提供了一个生成器,并通过随机交换列表 (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))
【讨论】:
【参考方案15】:你可以这样做:
(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])
但以我的拙见,你所拥有的一切都很好。它很有效,很简单
【讨论】:
以上是关于在 Python 中随机交错 2 个数组的主要内容,如果未能解决你的问题,请参考以下文章