簡體   English   中英

如何在尊重某些約束的同時生成一系列數字?

[英]How to generate a sequence of numbers while respecting some constraints?

我需要生成從 0 到 999999(不重復)的所有可能數字(整數),同時遵守一系列約束。

為了更好地理解要求,假設每個數字由 2 位前綴和 4 位后綴組成。 就像 000000 讀作 00-0000 和 999999 讀作 99-9999。 現在規則:

  • 前綴必須是隨機順序
  • 后綴必須按隨機順序排列,同時確保序列中的每 10k 個數字都包含從 0000 到 9999 的所有數字。
  • 給定種子,必須能夠以相同的順序再次生成數字。
  • 不是真正的要求,但如果使用 Linq 完成它會很棒。

到目前為止,我已經編寫了一些滿足除第一個要求之外的所有要求的代碼:

var seed = 102111;
var rnd = new Random(seed);
var prefix = Enumerable.Range(0, 100).OrderBy(p => rnd.Next());
var suffix = Enumerable.Range(0, 10000).OrderBy(s => rnd.Next());
var result = from p in prefix
                from s in suffix
                select p.ToString("d2") + s.ToString("d4");

foreach(var entry in result)
{
    Console.WriteLine(entry);
}

使用它,我可以使用相同的種子來重現序列,前 10000 個數字的所有數字都是從 0000 到 9999,第二個 10k 也是如此,依此類推,但前綴並不是真正隨機的,因為每個 10k 組都有相同的前綴。

我還想用數字創建一個類,它是一個組(100 個組,每個組有 10k 個數字)以使其更容易洗牌,但我相信這是更好、更簡單的方法。

[基於對問題的誤解,我已經覆蓋了一個早期的,錯誤的解決方案]。


我們首先創建一個輔助方法,根據給定的種子生成一個混洗范圍:

static IEnumerable<int> ShuffledRange(int size, int seed)
{
  var rnd = new Random(seed);
  return Enumerable.Range(0, size).OrderBy(p => rnd.Next());
}

接下來我們要做的是隨機化所有后綴並將它們全部放入序列中。 請注意,我們為每個shuffle使用不同的種子,但種子的值是可預測的。

static IEnumerable<string> ShuffledIds(int seed)
{
  const int s = 10000;
  const int p = 100;
  var suffixes = Enumerable.Range(0, p)
    .Select(seedOffset => ShuffledRange(s, seed + seedOffset)
    .SelectMany(x => x);

我們遇到了一個約束,即每個10000塊都有隨機順序的10000個后綴。 現在我們必須分配10000個每個前綴。 讓我們為每個可能的后綴制作一系列前綴。 (同樣,我們使用一個尚未使用的種子進行每次洗牌。)

  var dict = new Dictionary<int, IEnumerator<int>>();
  for (int suffix = 0; suffix < s; suffix += 1)
    dict[suffix] = ShuffledRange(p, seed + p + suffix).GetEnumerator();

現在我們可以分發它們了

  foreach(int suffix in suffixes)
  {
    dict[suffix].MoveNext();
    yield return dict[suffix].Current.ToString("d2") +
     suffix.ToString("d4");
  }
}

這應該做到這一點。

請注意,這也有一個很好的屬性,即洗牌算法不再是需要shuffle的代碼的關注點。 嘗試在輔助函數中封裝類似的細節。

使用ckuri發布的想法並包括Eric Lippert建議的改進,您可以按后綴對數字列表進行分組:

var prefixLength = 100;
var suffixLength = 10000;

 Enumerable
  .Range(0, prefixLength * suffixLength)
  .OrderBy(number => rnd.Next())
  .GroupBy(number => number % suffixLength)

然后,您可以展平列表:

Enumerable
 .Range(0, prefixLength * suffixLength)
 .OrderBy(number => rnd.Next())
 .GroupBy(number => number % suffixLength)
 .SelectMany(g => g)

在此之前,您將擁有一個數字列表,其中,在每100行(prefixLength)中,前綴將是相同的。 因此,您可以選擇它們,獲取每行的索引:

Enumerable
 .Range(0, prefixLength * suffixLength)
 .OrderBy(number => rnd.Next())
 .GroupBy(number => number % suffixLength)
 .SelectMany(g => g)
 .Select((g, index) => new { Index = index, Number = g })

使用索引信息,您可以使用prefixLength作為因子對應用mod函數的行進行分組:

Enumerable
 .Range(0, prefixLength * suffixLength)
 .OrderBy(number => rnd.Next())
 .GroupBy(number => number % suffixLength)
 .SelectMany(g => g)
 .Select((g, index) => new { Index = index, Number = g })
 .GroupBy(g => g.Index % prefixLength, g => g.Number)

最后,您可以再次展平列表,並將值轉換為字符串,以獲得最終結果:

Enumerable
 .Range(0, prefixLength * suffixLength)
 .OrderBy(number => rnd.Next())
 .GroupBy(number => number % suffixLength)
 .SelectMany(g => g)
 .Select((g, index) => new { Index = index, Number = g })
 .GroupBy(g => g.Index % prefixLength, g => g.Number)
 .SelectMany(g => g)
 .Select(number => $"{number/suffixLength:d2}{number%suffixLength:d4}")

此解決方案的靈感來自 Rodolfo Santos 的回答 它通過改組共享相同后綴的每個組內的數字來改進他的解決方案,完成結果序列的隨機性。 該算法利用了LINQ的OrderBy排序穩定這一事實,因此按前綴對數字進行排序不會隨機破壞先前的順序。 如果不是這種情況,則需要額外的分組和展平。

public static IEnumerable<int> RandomConstrainedSequence(
    int prefixLength, int suffixLength, int seed)
{
    var random = new Random(seed);
    return Enumerable
    .Range(0, prefixLength * suffixLength)
    .OrderBy(_ => random.Next()) // Order by random
    .OrderBy(n => n / suffixLength) // Order by prefix (randomness is preserved)
    .Select((n, i) => (n, i)) // Store the index
    .GroupBy(p => p.n % suffixLength) // Group by suffix
    // Suffle the numbers inside each group, and zip with the unsuffled stored indexes
    .Select(g => g.OrderBy(_ => random.Next()).Zip(g, (x, y) => (x.n, y.i)))
    .SelectMany(g => g) // Flatten the sequence
    .OrderBy(p => p.i) // Order by the stored index
    .Select(p => p.n); // Discard the index and return the number
}

用法示例:

int index = 0;
foreach (var number in RandomConstrainedSequence(5, 10, 0))
{
    Console.Write($"{number:00}, ");
    if (++index % 10 == 0) Console.WriteLine();
}

輸出:

44, 49, 47, 13, 15, 00, 02, 01, 16, 48,
25, 30, 29, 41, 43, 32, 38, 46, 04, 17,
23, 19, 35, 28, 07, 34, 20, 31, 26, 12,
36, 10, 22, 08, 27, 21, 24, 45, 39, 33,
42, 18, 09, 03, 06, 37, 40, 11, 05, 14,


更新:這個解決方案可以推廣到解決更大范圍的問題,其中排序被限制在一個序列的每個子組中。 這是一個完全做到這一點的擴展方法:

public static IEnumerable<TSource> OrderGroupsBy<TSource, TGroupKey, TOrderKey>(
    this IEnumerable<TSource> source,
    Func<TSource, TGroupKey> groupByKeySelector,
    Func<TSource, TOrderKey> orderByKeySelector)
{
    return source
        .Select((x, i) => (Item: x, Index: i))
        .GroupBy(e => groupByKeySelector(e.Item))
        .Select(group =>
        {
            var itemsOrdered = group.Select(e => e.Item).OrderBy(orderByKeySelector);
            var indexesUnordered = group.Select(e => e.Index);
            return itemsOrdered.Zip(indexesUnordered, (x, i) => (Item: x, Index: i));
        })
        .SelectMany(group => group)
        .OrderBy(pair => pair.Index)
        .Select(pair => pair.Item);
}

用不同的例子可以更清楚地看到這種方法的效果。 名稱數組是有序的,但順序受限於以相同字母開頭的每個名稱子組內:

var source = new string[] { "Ariel", "Billy", "Bryan", "Anton", "Alexa", "Barby" };
Console.WriteLine($"Source: {String.Join(", ", source)}");
var result = source.OrderGroupsBy(s => s.Substring(0, 1), e => e);
Console.WriteLine($"Result: {String.Join(", ", result)}");
Source: Ariel, Billy, Bryan, Anton, Alexa, Barby
Result: Alexa, Barby, Billy, Anton, Ariel, Bryan

使用這種擴展方法,原來的問題可以這樣解決:

public static IEnumerable<int> RandomConstrainedSequence(
    int prefixLength, int suffixLength, int seed)
{
    var random = new Random(seed);
    return Enumerable
        .Range(0, prefixLength * suffixLength)
        .OrderBy(_ => random.Next()) // Order by random
        .OrderBy(n => n / suffixLength) // Order again by prefix
        // Suffle each subgroup of numbers sharing the same suffix
        .OrderGroupsBy(n => n % suffixLength, _ => random.Next());
}

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM