根据您在 cmets 中的答案,我仍然不明白您到底需要什么。 AFAIU 你有两条数据:场景和高度;并且您想要生成复合(场景,高度)元素的排列。我假设您需要:
只生成一次所有可能排列的随机列表
生成一个长的(可能是无限的)随机不同排列的流
所以这里有一些代码可能会有所帮助。
首先让我们定义一些样板:
public class Scene
{
public readonly string Something;
public Scene(string something)
{
Something = something;
}
// something else
}
public struct CompoundSceneData
{
public readonly Scene Scene;
public readonly float Height;
public CompoundSceneData(Scene scene, float height)
{
Scene = scene;
Height = height;
}
}
当然你的Scene 类很可能更复杂。 CompoundSceneData是一个结构体,表示场景+高度的单项。
#1只生成一次所有可能排列的随机列表:
// Fisher–Yates shuffle of indices 0..size
int[] GenerateRandomIndicesPermutation(int size)
{
int[] permutation = Enumerable.Range(0, size).ToArray();
Random rnd = new Random();
for (int cur = size; cur >= 2; cur--)
{
int swapPos = rnd.Next(cur);
int tmp = permutation[swapPos];
permutation[swapPos] = permutation[cur - 1];
permutation[cur - 1] = tmp;
}
return permutation;
}
List<CompoundSceneData> GenerateAllRandomPermutationsOnce(Scene[] scenes, float[] heights)
{
int scenesCount = scenes.Length;
int heightsCount = heights.Length;
int totalCount = scenesCount * heightsCount;
List<CompoundSceneData> permutations = new List<CompoundSceneData>(totalCount);
foreach (var compoundIndex in GenerateRandomIndicesPermutation(totalCount))
{
int sceneIndex = compoundIndex % scenesCount;
int heightIndex = compoundIndex / scenesCount;
permutations.Add(new CompoundSceneData(scenes[sceneIndex], heights[heightIndex]));
}
return permutations;
}
void TestUsageAllOnce()
{
Scene[] scenes = new Scene[] { new Scene("Scene #1"), new Scene("Scene #2") };
float[] heights = new float[] { 0.1f, 0.2f, 0.3f };
// this is effectively endless loop
foreach (CompoundSceneData sceneData in GenerateAllRandomPermutationsOnce(scenes, heights))
{
// will be called excactly 2*3 = 6 times
DrawScene(sceneData);
}
}
这里有几个关键的想法:
如果我们有 N 个场景和 M 个高度,则会有 NM 个排列,并且给定一个范围为 [0, NM-1] 的数字,您可以选择一对。例如,2*N + 5 表示第 5 个场景 + 第 2 个高度(在从 0 开始的索引中(!))。
因此,如果我们要生成一系列不同的 N 个场景和 M 个高度的序列,生成数字 [0, N*M-1] 的随机排列并将其用作索引序列就足够了
有一种众所周知的Fisher–Yates shuffle 算法可以创建随机排列。
#2 生成无限的随机不同排列流:
IEnumerable<CompoundSceneData> GenerateInfiniteRandomStream(Scene[] scenes, float[] heights)
{
Random rnd = new Random();
while (true)
{
int sceneIndex = rnd.Next(scenes.Length);
int heightIndex = rnd.Next(heights.Length);
yield return new CompoundSceneData(scenes[sceneIndex], heights[heightIndex]);
}
}
void TestUsageInfinite()
{
Scene[] scenes = new Scene[] { new Scene("Scene #1"), new Scene("Scene #2") };
float[] heights = new float[] { 0.1f, 0.2f, 0.3f };
// this is effectively endless loop
foreach (CompoundSceneData sceneData in GenerateInfiniteRandomStream(scenes, heights))
{
DrawScene(sceneData);
// this is the only thing that will stop the loop
if (IsEndOfGame)
break;
}
}
void TestUsageInfinite2()
{
Scene[] scenes = new Scene[] { new Scene("Scene #1"), new Scene("Scene #2") };
float[] heights = new float[] { 0.1f, 0.2f, 0.3f };
List<CompoundSceneData> fixedSizeList = GenerateInfiniteRandomStream(scenes, heights).Take(100).ToList();
foreach (CompoundSceneData sceneData in fixedSizeList)
{
// this will be called 100 times (as specified in Take)
DrawScene(sceneData);
}
}
这里唯一有趣的是 C# 功能 yield return 的使用。此功能允许从看起来顺序的代码创建数据流 (IEnumerable)。
请注意,对于解决方案 #2,不能保证每个组合(场景+数据)在每 (N*M) 个项目中只会出现一次。它只是生成随机组合,这些组合仅在长期内具有良好的统计特性。也可以实现这种保证,但它会使代码变得非常复杂,并且用户可能无论如何都不会注意到。