随机拆分列表,将原始订单保留在新列表中

时间:2012-04-21 04:37:12

标签: python

我很难制定我的问题,所以我只是举例说明。

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

7 个答案:

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