3

質問をまとめるのに苦労しているので、例を挙げて説明します。

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

元の順序を維持しながら、リストを 2 つのランダム サンプルに分割する簡単な方法はありますか?


編集: random.sample を使用して並べ替えることができることは知っていますが、簡単でシンプルな 1 行の方法を望んでいました。

編集 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
4

7 に答える 7

4

このアプローチはどうですか。インデックスからランダムにサンプリングし、2つのリスト内包表記から2つのリストを返します(含まれている場合含まれていない場合)

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
于 2012-04-21T06:27:57.543 に答える
4

サンプリングと並べ替えよりも単純な方法でランダム性を維持しながら、分割後に制限を行い、並べ替えを行わないことは不可能だと思います。

制限がない場合は、RNG がリストを反復処理し、値を送信する宛先リストをランダムに選択することで、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]

ある程度の偏りに対処できる場合は、制限に達したときに最初のリストへの追加を停止しても、上記のソリューションを引き続き使用できます。例のように小さなリストを扱う場合、最初のリストの長さが正しくなるまで再試行することは大したことではありません。

本当にランダムにして、最初のリストのサイズを制限できるようにしたい場合は、あきらめて、リストの少なくとも 1 つを並べ替える必要があります。私が考えることができるワンライナー実装に最も近いのは、次のようなものです。

>>> 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 の順序は保持されています。

編集

では、再注文を絶対に避けなければならないのでしょうか? 多くのきちんとした解決策が投稿されており、2 番目の解決策は非常に優れていますが、どれも以下よりも単純で簡単で短いものではありません。

def random_split(items, size):
    sample = set(random.sample(items, size))
    return sorted(sample), sorted(set(items) - sample)

両方の並べ替え操作を考慮しても、単純さと効率の点でそれを打ち負かすのは難しいと思います。Python の Timsort がどのように最適化されているか、および他のほとんどのメソッドがリストごとに n 個の項目を少なくとも 1 回反復する必要があることを考慮してください。

本当に並べ替えを避ける必要がある場合は、これも機能し、非常に簡単でシンプルですが、2回反復します。

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

これは本質的に、senderle が比較 O(1) を行うために提案した set(sample) を使用した Hexparrot のソリューションと同じであり、冗長なインデックス サンプルと列挙呼び出しを削除します。ハッシュ可能なオブジェクトのみを扱う場合は必要ありません。

于 2012-04-21T04:43:34.617 に答える
3

興味深い提案がたくさんありましたが、そのうちの 1 つをこの投稿の以前のバージョンでうっかり複製してしまいました。ただし、この正確な形式で提示されていない 2 つのソリューションを次に示します。

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

これは、リストを 1 回通過するだけで、順序を維持しながら、リストの均一にランダムなパーティションを生成します。これは、私がうっかり複製してしまった hexparrot の提案を改良したものです。三項演算子と 2 つの個別のリストを使用することもできますが、これは少しすっきりしているように思えます。を使用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 の 2 回目の編集を改良したものです。それほど違いはありませんが、リストのリストをシャッフルする代わりに、ブールのリストをシャッフルします。初見の方が分かりやすいと思います。random.sampleコピーを生成するを使用して、2 行のシャッフルを回避します。2 行のシャッフルを好む人もいるかもしれませんが、簡単に交換できます。

これらはどちらも同じ基本原則に基づいて機能することに注意してください。一連のブール値を生成し、それらを使用してleft_rightタプルにインデックスを付けます。ブール値のリストを事前に生成することで、最初のものは簡単に 2 番目のものとほとんど同じにすることができます。

最後に、2 番目の解決策は、非常に醜い「ワンライナー」に変換できますが、これは明らかにお勧めしませんが、娯楽​​と嘲笑のためにここに表示します。

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), ([], []))
于 2012-04-21T22:24:57.817 に答える
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])
于 2012-04-21T05:03:57.990 に答える
1

シャッフルソートテーマのバリエーション...

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:])])
于 2012-04-21T14:31:25.980 に答える
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']
于 2012-04-21T05:29:19.290 に答える
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))]
于 2012-04-21T16:27:40.013 に答える