8

Pythonのモジュール「random」には機能がありますrandom.choice

random.choice(seq)
空でないシーケンス seq からランダムな要素を返します。seqが空の場合、 が発生しIndexErrorます。

これを .NET でエミュレートするにはどうすればよいですか?

public T RandomChoice<T> (IEnumerable<T> source)

編集: これは数年前にインタビューの質問として聞いたものですが、今日、この問題は私の仕事で自然に発生しました。インタビューの質問は制約付きで述べられた

  • 「シーケンスが長すぎてメモリに保存できません」
  • 「シーケンスを 1 回だけループできます」
  • 'the sequence doesn't have a length/count method' (ala .NET IEnumerable)
4

7 に答える 7

14

ソースを 1 回だけ反復し、一時的に格納するためにメモリを割り当てる必要がないメソッドを作成するには、反復したアイテムの数を数え、現在のアイテムが結果になる確率を決定します。

public T RandomChoice<T> (IEnumerable<T> source) {
  Random rnd = new Random();
  T result = default(T);
  int cnt = 0;
  foreach (T item in source) {
    cnt++;
    if (rnd.Next(cnt) == 0) {
      result = item;
    }
  }
  return result;
}

最初のアイテムにいるとき、それが使用される確率は 1/1 です (これまでに見た唯一のアイテムであるため)。2 番目のアイテムにいる場合、最初のアイテムを置き換える確率は 1/2 です。


dasblinkenlightが指摘したように、アイテムを選択するための単一の乱数ではなく、アイテムごとに1つの乱数を作成するため、これは当然、もう少し多くのCPUを使用します。Dan Tao が提案したように、ソースが を実装しているかどうかを確認IList<T>し、その機能を使用してコレクションの長さを取得し、インデックスでアイテムにアクセスする実装を使用できます。

public T RandomChoice<T> (IEnumerable<T> source) {
  IList<T> list = source as IList<T>;
  if (list != null) {
    // use list.Count and list[] to pick an item by random
  } else {
    // use implementation above
  }
}

注:Randomインスタンスをメソッドに送信することを検討する必要があります。それ以外の場合は、シードが現在の時間から作成されるため、メソッドをあまりにも近い時間に 2 回呼び出すと、同じランダム シードが取得されます。


0 ~ 9 を含む配列から 1 つの数値を 1000000 回選択して、選択した数値の分布が歪んでいないことを示すテスト実行の結果:

0: 100278
1: 99519
2: 99994
3: 100327
4: 99571
5: 99731
6: 100031
7: 100429
8: 99482
9: 100638
于 2012-07-03T16:16:11.190 に答える
6

シーケンスを 2 回 (カウント用に 1 回、要素用に 1 回) 繰り返さないようにするには、ランダムな要素を取得する前にシーケンスを配列に保存することをお勧めします。

public static class RandomExt {
    private static Random rnd = new Random();
    public static T RandomChoice<T> (this IEnumerable<T> source) {
        var arr = source.ToArray();   
        return arr[rnd.Next(arr.Length)];
    }
    public static T RandomChoice<T> (this ICollection<T> source) {
        return source[rnd.Next(rnd.Count)];
    }
}

編集Chris Sinclair による非常に優れたアイデアを実装しました。

于 2012-07-03T16:10:39.347 に答える
2
        public T RandomChoice<T> (IEnumerable<T> source)
        {
            if (source == null)
            {
                throw new ArgumentNullException("source");
            }

            var list = source.ToList();

            if (list.Count < 1)
            {
                throw new MissingMemberException();
            }

            var rnd = new Random();
            return list[rnd.Next(0, list.Count)];
        }

または拡張子

    public static T RandomChoice<T> (this IEnumerable<T> source)
    {
        if (source == null)
        {
            throw new ArgumentNullException("source");
        }

        var list = source.ToList();

        if (list.Count < 1)
        {
            throw new MissingMemberException();
        }

        var rnd = new Random();
        return list[rnd.Next(0, list.Count)];
    }
于 2012-07-03T16:12:47.147 に答える
2
private static Random rng = new Random();

...
return source.Skip(rng.next(source.Count())).Take(1);
于 2012-07-03T16:10:03.340 に答える
1

私はdasblinkenlightの答えsourceに行きます.1つの小さな変更があります.すでにインデックス付きのコレクションである可能性があるという事実を活用します.

public static class RandomExt
{
    public static T Choice<T>(this Random random, IEnumerable<T> sequence)
    {
        var list = sequence as IList<T> ?? sequence.ToList();
        return list[random.Next(list.Count)];
    }
}

質問で参照したPythonバージョンとの一貫性を高めるために、上記の回答からインターフェイスも変更したことに注意してください。

var random = new Random();
var numbers = new int[] { 1, 2, 3 };
int randomNumber = random.Choice(numbers);

編集:実際には、グッファの答えがさらに気に入っています。

于 2012-07-03T16:14:43.000 に答える
0

拡張メソッドがあると仮定しますIEnumerable.MinBy:

var r = new Random();
return source.MinBy(x=>r.Next())

メソッドMinByはシーケンスをメモリに保存せず、IEnumerable.Min1 回の反復を行うように機能します ( MoreLinqまたは他の場所を参照) 。

于 2012-07-04T10:59:02.563 に答える
0

さて、シーケンス内のすべての要素のリストを取得します。乱数ジェネレーターにインデックスを要求し、インデックスごとに要素を返します。Sequence とは何かを定義します - IEnumerable が最も明白ですが、乱数ジェネレーターの要素数を知るには、それをリストに具体化する必要があります。これはところで、エミュレートではなく、実装です。

これは宿題の初級コースの質問ですか?

于 2012-07-03T16:09:31.100 に答える