我很难制定我的问题,所以我只是举例说明。
x = ['abc', 'c', 'w', 't', '3']
a, b = random_split(x, 3) # first list should be length 3
# e.g. a => ['abc', 'w', 't']
# e.g. b => ['c', '3']
是否有一种简单的方法可以将列表拆分为两个随机样本,同时保持原始排序?
编辑:我知道我可以使用random.sample然后重新排序,但我希望有一个简单,简单的单行方法。
编辑2:这是另一种解决方案,看看你是否可以改进它:
def random_split(l, a_size):
a, b = [], []
m = len(l)
which = ([a] * a_size) + ([b] * (m - a_size))
random.shuffle(which)
for array, sample in zip(which, l):
array.append(sample)
return a, b
编辑3:我对避免排序的担心是在最好的情况下它是O(N*log(N))
。应该可以得到一个可以缩放O(N)
的函数。不幸的是,到目前为止发布的解决方案实际上都没有实现O(N)
但是,经过一番思考后我发现一个有效且与@ PedroWerneck的答案相当的性能。虽然,我并非100%确定这是真正随机的。
def random_split(items, size):
n = len(items)
a, b = [], []
for item in items:
if size > 0 and random.random() < float(size)/n:
b.append(item)
size -= 1
else:
a.append(item)
n -= 1
return a, b
答案 0 :(得分:4)
我相信在拆分后不可能进行限制和排序,同时保持随机性的方式比采样和重新排序更简单。
如果没有限制,它可以像RNG一样随机迭代列表,并随机选择将值发送到的目的地列表:
>>> import random
>>> x = range(20)
>>> a = []
>>> b = []
>>> for v in x:
... random.choice((a, b)).append(v)
...
>>> a
[0, 2, 3, 4, 6, 7, 10, 12, 15, 17]
>>> b
[1, 5, 8, 9, 11, 13, 14, 16, 18, 19]
如果您可以处理某些偏差,则可以在达到限制时停止附加到第一个列表,并仍使用上述解决方案。如果你将处理你的例子中的小列表,那么在你获得第一个列表长度之前重试它应该不是什么大问题。
如果您希望它非常随机并且能够限制第一个列表大小,那么您将不得不放弃并重新排序至少一个列表。我认为最接近单线程实现的是:
>>> x = range(20)
>>> b = x[:]
>>> a = sorted([b.pop(b.index(random.choice(b))) for n in xrange(limit)])
>>> a
[0, 1, 5, 10, 15, 16, 17]
>>> b
[2, 3, 4, 6, 7, 8, 9, 11, 12, 13, 14, 18, 19]
你必须对a进行排序,但b保留了顺序。
修改强>
现在,你真的必须不惜一切代价避免重新排序吗?发布了许多简洁的解决方案,您的第二个解决方案非常好,但没有一个比以下更简单,更容易和更短:
def random_split(items, size):
sample = set(random.sample(items, size))
return sorted(sample), sorted(set(items) - sample)
即使考虑到两种排序操作,我认为为了简单和高效,很难击败那种。考虑优化Python的Timsort是如何优化的,以及大多数其他方法如何为每个列表至少迭代n个项目。
如果你真的必须避免重新排序,我想这个也很有效,非常简单,但是迭代两次:
def random_split(items, size):
sample = set(random.sample(items, size))
a = [x for x in items if x in sample]
b = [x for x in items if x not in sample]
return a, b
这与Hexparrot的解决方案基本相同,发送者建议使用set(样本)进行比较O(1),并删除冗余索引样本并枚举调用。如果只处理可散列对象,则不需要它。
答案 1 :(得分:4)
这种方法怎么样?来自索引的随机样本,并返回两个列表推导中的两个列表如果在中,如果不在中:
def random_split(lst, size):
import random
samp = set(random.sample(xrange(len(lst)),size))
return ([v for i,v in enumerate(lst) if i in samp],
[v for i,v in enumerate(lst) if i not in samp])
x = ['abc', 'c', 'w', 't', '3']
print random_split(x,3)
返回
(['abc', 't', '3'], ['c', 'w']) #random and retains order
答案 2 :(得分:3)
好的,有很多有趣的建议,其中一个我在这篇文章的前一版本中无意中重复了。但是这里有两个没有以完全形式呈现的解决方案:
def random_split(seq, n):
indices = set(random.sample(range(len(seq)), n))
left_right = ([], [])
for n, x in enumerate(seq):
left_right[n not in indices].append(x)
return left_right
这只会传递一个列表,并生成列表的统一随机分区,维护顺序。这是对hexparrot建议的改进,这是我无意中重复的建议。您可以使用三元运算符和两个单独的列表,但这对我来说似乎有点清晰。使用enumerate
允许此处理不可清除的项目以及具有重复项目的序列。
def random_split(seq, n):
rnd_bools = random.sample((0,) * n + (1,) * (len(seq) - n), len(seq))
left_right = ([], [])
for b, x in zip(rnd_bools, seq):
left_right[b].append(x)
return left_right
这个人对我感觉正确。这是Jacob Eggers对问题的第二次编辑的改进。它并没有什么不同,但它不是改组列表,而是洗牌列表。我认为乍一看有点难以理解。它通过使用生成副本的random.sample
来避免2行洗牌;有些人可能更喜欢2线洗牌,而且很容易更换。
请注意,这两者都基于相同的基本原则:生成一系列bool并使用它们来索引left_right
元组;通过预生成布尔列表,第一个可以很容易地与第二个几乎相同。
最后,第二个解决方案可以转换为非常丑陋的“单行”,我不建议 - 显然 - 但是我在这里展示你的娱乐和嘲笑:
random_split = lambda seq, n: reduce(lambda a, b: (a[0] + ([b[1]] if not b[0] else []), a[1] + ([b[1]] if b[0] else [])), zip(random.sample((0,) * n + (1,) * (len(seq) - n), len(seq)), seq), ([], []))
答案 3 :(得分:1)
以下是您可以转换为函数的成绩单:
>>> a = [10,20,30,40,50,60]
>>> keep = sorted(random.sample(range(len(a)),3))
>>> keep
[0, 3, 4]
>>> ([a[i] for i in keep], [a[i] for i in range(len(a)) if i not in keep])
([10, 40, 50], [20, 30, 60])
答案 4 :(得分:1)
shuffle-sort主题的变体......
def random_split(L, size):
index = range(len(L))
random.shuffle(index)
return ([L[i] for i in sorted(index[:size])],
[L[i] for i in sorted(index[size:])])
答案 5 :(得分:0)
我猜你的random_split不应该给重复元素。
如果您在原始列表中没有任何重复项,那么这将在原始帖子中将用作单行,但它使用排序。这是一种非常简单但效率低下的方法:
import random
x = ['abc', 'c', 'w', 't', '3']
def random_split(x, n):
k = x[:]
random.shuffle(k)
yield sorted(k[:n], key = x.index)
yield sorted(k[n:], key = x.index)
a, b = random_split(x, 3)
结果示例:
>>> a
['c', 'w', 't']
>>> b
['abc', '3']
答案 6 :(得分:0)
以下是几行内容:
from random import sample
x = ['abc', 'c', 'w', 't', '3']
sample_size = len(x) // 2
sample_set = set(sample(x, sample_size))
split_list = [[x[i] for i in subset] for subset in (sorted(sample_set), sorted(set(x) - sample_set))]