8

続行するコードを使用して、数値のコレクションを生成し、配列内の要素の位置をシャッフルすることに成功しました。

var randomNumbers = Enumerable.Range(0, 100)
                    .OrderBy(x => Guid.NewGuid());

すべてが正常に機能しますが、理解しようとすると、私の作品にスパナが投げ込まれますEnumerable.OrderBy。たとえば、次のコードを考えてみましょう。

var pupils = new[] 
{ 
    new Person() { Name = "Alex", Age = 17 },
    new Person() { Name = "Jack", Age = 21 } 
};

var query = pupils.OrderBy(x => x.Age);

並べ替えたいプロパティを渡していることは理解しており、2番目のオーバーロードに明示的に指定されていない場合、LINQがComparer<T>.Defaultコレクションの順序付け方法を決定するために使用すると思います。IComparerこのような方法で配列をシャッフルするために、この合理的なロジックをどのように適用できるのか、私は本当にわかりません。では、LINQではどのようにしてこのような配列をシャッフルできますか?

4

3 に答える 3

7

Enumerable.OrderBy は keySelector をどのように使用しますか?

Enumerable.OrderBy<T>遅延リターン - keySelector は直接呼び出されません。結果は、IOrderedEnumerable<T>列挙されたときに順序付けを実行する です。

列挙されると、keySelector は要素ごとに 1 回呼び出されます。キーの順序は、要素の新しい順序を定義します。

これは気の利いた実装例です。


LINQ では、このように配列をシャッフルするにはどうすればよいでしょうか。

var randomNumbers = Enumerable
  .Range(0, 100)
  .OrderBy(x => Guid.NewGuid());

各要素に対して Guid.NewGuid が呼び出されます。2 番目の要素の呼び出しは、最初の要素の呼び出しよりも高い値または低い値を生成する場合があります。

randomNumbersIOrderedEnumerable<int>列挙されるたびに異なる順序を生成する です。randomNumbersKeySelector は、要素が列挙されるたびに要素ごとに 1 回呼び出されます。

于 2013-01-02T17:55:58.997 に答える
4

あなたはそのようなシャッフルがどのように機能するかを理解するのにかなり近づいています..あなたの2番目のケースでは

pupils.OrderBy(x => x.Age);

Comparer<int>.Default使用されます (人物はAge、簡単に並べ替えられます)。

最初のケースでは、Comparer<Guid>.Defaultが使用されます。

さて、それはどのように機能しますか?.

(おそらく)行うたびにGuid.NewGuid()、異なる/オリジナル/非複製Guidが生成されます。今あなたがするとき

var randomNumbers = Enumerable.Range(0, 100).OrderBy(x => Guid.NewGuid());

番号は、生成された Guid に基づいてソートされます。

GUIDとは何ですか?

これらは、16 進形式で表される 128 ビットの整数です。2^128 は非常に大きな数であるため、2 つの Guid を生成する可能性は非常にまれであり、ほとんど不可能です。Guid はある種のランダム性を示すため、順序もランダムになります。

順序を強制するために 2 つの Guid をどのように比較しますか?

簡単な実験で確認できます。行う:

var guids = Enumerable.Range(0, 10).Select((x, i) => 
    {
        Guid guid = Guid.NewGuid();
        return new { Guid = guid, NumberRepresentation = new BigInteger(guid.ToByteArray()), OriginalIndex = i };
    }).ToArray();

var guidsOrderedByTheirNumberRepresentation = guids.OrderBy(x => x.NumberRepresentation).ToArray();
var guidsOrderedAsString = guids.OrderBy(x => x.Guid.ToString()).ToArray();

var randomNumbers = Enumerable.Range(0, 10).OrderBy(x => guids[x].Guid).ToArray();

//print randomNumbers.SequenceEqual(guidsOrderedByTheirNumberRepresentation.Select(x => x.OriginalIndex)) => false

//print randomNumbers.SequenceEqual(guidsOrderedAsString.Select(x => x.OriginalIndex)) => true

そのComparer<Guid>.Defaultため、GUID の文字列表現に基づいています。


余談:

速度を上げるには、 Fisher-Yatesシャッフルを使用する必要があります。多分

public static IEnumerable<T> Shuffle<T>(this IList<T> lst)
{
    Random rnd = new Random();
    for (int i = lst.Count - 1; i >= 0; i--)
    {
        int j = rnd.Next(i + 1);
        yield return lst[j];
        lst[j] = lst[i];
    }
}

または簡潔にするために、(Guidアプローチよりもまだ高速になる可能性があります)

public static IEnumerable<T> Shuffle<T>(this IEnumerable<T> lst)
{
    Random rnd = new Random();
    return lst.OrderBy(x => rnd.Next());
}
于 2013-01-02T10:06:03.880 に答える
2

では、これはどのように機能しますか?

次のクエリはComparer<Guid>.Default比較に使用します。

  .OrderBy(x => Guid.NewGuid())

生成されたすべてのGUIDは(句自体で生成しているため)実質的に一意であるOrderByため、ランダムな順序になっていると信じています(これは誤った理解です)。
クエリを再度実行すると、新しいGUIDのセットが生成されるため、(おそらく)シャッフルされた結果が再び表示されます。

事前定義されたGUIDを使用する場合は、順序が表示されます。

randomNumbers1randomNumbers2以下の同じ値があります。

var randomGuids = Enumerable.Range(0,10).Select (x => Guid.NewGuid()).ToArray();

var randomNumbers1 = Enumerable.Range(0, 10).OrderBy(x => randomGuids[x]);

var randomNumbers2 = Enumerable.Range(0, 10).OrderBy(x => randomGuids[x]);

このような方法で配列をシャッフルするために、この合理的なロジックをどのように適用できるのか、私は本当にわかりません。

要素間に順序がないため(GUID例では)、シャッフルできます。順序付けされた要素を使用すると、シャッフルされた出力ではなく、順序付けられた出力が得られます。

于 2013-01-02T08:24:20.790 に答える