如何根据需要最好地生成随机数的静态数组?

时间:2010-06-28 17:42:20

标签: c# algorithm random

我正在处理的应用程序需要一个随机数矩阵。矩阵可以随时在任何方向上生长,并不总是满的。 (我可能最终会用四叉树或其他东西重新实现它,而不是带有大量空对象的矩阵。)

我需要一种方法来生成相同的矩阵,给定相同的种子,无论我以何种顺序计算矩阵。

LazyRandomMatrix rndMtx1 = new LazyRandomMatrix(1234) // Seed new object
float X = rndMtx1[0,0] // Lazily generate random numbers on demand
float Y = rndMtx1[3,16]
float Z = rndMtx1[23,-5]

Debug.Assert(X == rndMtx1[0,0])
Debug.Assert(Y == rndMtx1[3,16])
Debug.Assert(Z == rndMtx1[23,-5])

LazyRandomMatrix rndMtx2 = new LazyRandomMatrix(1234) // Seed second object
Debug.Assert(Y == rndMtx2[3,16])  // Lazily generate the same random numbers
Debug.Assert(Z == rndMtx2[23,-5]) // on demand in a different order
Debug.Assert(X == rndMtx2[0,0])

是的,如果我知道数组的维度,最好的方法是生成整个数组,只返回值,但它们需要独立生成并按需生成。

我的第一个想法是为每次调用新坐标初始化一个新的随机数生成器,用整个矩阵的种子的一些散列和调用中使用的坐标来播种它,但这似乎是一个可怕的黑客,因为它会需要创建大量新的Random对象。

8 个答案:

答案 0 :(得分:4)

您所谈论的内容通常称为“Perlin Noise”,这里有一个链接:http://freespace.virgin.net/hugo.elias/models/m_perlin.htm

该文章中最重要的是2D中的噪声函数:

  function Noise1(integer x, integer y)
    n = x + y * 57
    n = (n<<13) ^ n;
    return ( 1.0 - ( (n * (n * n * 15731 + 789221) + 1376312589) & 7fffffff) / 1073741824.0);    
  end function

它会根据x和y coordonates单独返回介于-1.0和+1.0之间的数字(以及一个硬编码的种子,您可以在应用程序的开头随机更改或保持原样)。

本文的其余部分是关于插入这些数字,但根据您想要这些数字的随机性,您可以保持原样。请注意,这些数字将是完全随机的。如果您改为应用余弦插值器并使用每5-6个索引生成的噪声,插入其间,您将获得高度图数据(这是我用过的)。跳过它来获取完全随机的数据。

答案 1 :(得分:2)

标准随机生成器通常是序列的生成器,其中每个下一个元素都是从之前构建的。因此要生成rndMtx1[3,16],您必须生成序列中的所有先前元素 实际上你需要一些与随机 generator 不同的东西,因为你只需要一个值,而不是序列。您必须构建自己的“生成器”,它使用种子和索引作为公式的输入来生成单个随机值。你可以发明很多方法来做到这一点。最简单的方法之一是取随机值asm hash(seed + index)(我想在密码和签名中使用哈希的想法是从输入数据中产生一些稳定的“随机”值)。

P.S。您可以通过制作延迟的矩阵块来改进使用独立生成器(Random(seed + index))的方法。

答案 2 :(得分:2)

我认为你实例化一个新的Random对象的第一个想法是在一些确定性散列(x坐标,y坐标,LazyRandomMatrix种子)中播种,这对于大多数情况来说可能是合理的。通常,在托管堆上创建大量小对象是CLR非常擅长高效处理的事情。我不认为Random.ctor()非常昂贵。如果需要考虑,您可以轻松衡量绩效。

一个非常类似的解决方案可能比创建一个好的确定性散列更容易,每次使用两个Random对象。类似的东西:

public int this[int x, int y]
{
    get
    {
        Random r1 = new Random(_seed * x);
        Random r2 = new Random(y);
        return (r1.Next() ^ r2.Next());
    }
}

答案 3 :(得分:2)

PRNGs can be built out of hash functions.
这就是例如MS Research在GPU上并行化随机数生成with MD5 others with TEA (实际上,PRNG可以被认为是来自(种子,状态)=&gt; nextnumber的哈希函数。)
在GPU上生成大量随机数会带来类似的问题 (例如,为了使其平行,不应该有一个共享状态。)

虽然在加密世界中使用加密哈希更常见,但为了速度和简单性,我冒昧地使用MurmurHash 2.0。它具有非常好的统计特性作为散列函数。相关但不完全相同的测试显示它将good results作为PRNG。 (除非我在C#代码中有fsc#kd,就是。:)随意使用任何其他合适的哈希函数;加密的(MD5,TEA,SHA) - 尽管加密哈希值往往要慢得多。

public class LazyRandomMatrix
{
    private uint seed;

    public LazyRandomMatrix(int seed)
    {
        this.seed = (uint)seed;
    }

    public int this[int x, int y]
    {
        get
        {
            return MurmurHash2((uint)x, (uint)y, seed);
        }
    }

    static int MurmurHash2(uint key1, uint key2, uint seed)
    {
        // 'm' and 'r' are mixing constants generated offline.
        // They're not really 'magic', they just happen to work well.

        const uint m = 0x5bd1e995;
        const int r = 24;

        // Initialize the hash to a 'random' value

        uint h = seed ^ 8;

        // Mix 4 bytes at a time into the hash

        key1 *= m;
        key1 ^= key1 >> r;
        key1 *= m;

        h *= m;
        h ^= key1;

        key2 *= m;
        key2 ^= key2 >> r;
        key2 *= m;

        h *= m;
        h ^= key2;

        // Do a few final mixes of the hash to ensure the last few
        // bytes are well-incorporated.

        h ^= h >> 13;
        h *= m;
        h ^= h >> 15;

        return (int)h;
    }

}

答案 4 :(得分:2)

这是一个基于SHA1哈希的解决方案。基本上,这需要X,Y和Seed值的字节,并将其打包成一个字节数组。然后是字节数组的散列和用于生成int的散列的前4个字节。这应该是随机的。

public class LazyRandomMatrix 
{
  private int _seed;
  private SHA1 _hashProvider = new SHA1CryptoServiceProvider();

  public LazyRandomMatrix(int seed)
  {
    _seed = seed;
  }

  public int this[int x, int y]
  {
    get
    {
      byte[] data = new byte[12];
      Buffer.BlockCopy(BitConverter.GetBytes(x), 0, data, 0, 4);
      Buffer.BlockCopy(BitConverter.GetBytes(y), 0, data, 4, 4);
      Buffer.BlockCopy(BitConverter.GetBytes(_seed), 0, data, 8, 4);

      byte[] hash = _hashProvider.ComputeHash(data);
      return BitConverter.ToInt32(hash, 0);
    }
  }     
}

答案 5 :(得分:1)

伪随机数生成器本质上是一个确定性地计算给定值的后继的函数。

您可以发明一种简单的算法来计算邻居的值。如果邻居还没有值,请先从其各自的邻居计算其值。

这样的事情:

  • value (0,0) = seed
  • value (x + 1,0) = successor(value (x,0)
  • value (x,y + 1) = successor(value (x,y)

示例 successor(n)= n + 1 计算值(2,4)

 \ x  0      1      2
y  +-------------------
 0 | 627    628    629
 1 |               630
 2 |               631
 3 |               632
 4 |               633

这个示例算法显然不是很好,但你明白了。

答案 6 :(得分:1)

您希望随机数生成器可以随机访问元素,而不是顺序访问。 (注意,您可以将两个坐标缩减为单个索引,即通过计算i = x +(y <&lt; 16)。)

这种生成器的一个很酷的例子是Blum Blum Shub,它是一个加密安全的PRNG,具有简单的随机访问。不幸的是,它很慢。

一个更实际的例子是众所周知的线性同余生成器。您可以轻松修改一个以允许随机访问。考虑定义:

X(0) = S
X(n) = B + X(n-1)*A (mod M)

直接评估这将花费O(n)时间(即pseudo linear,而非线性),但您可以转换为非递归形式:

//Expand a few times to see the pattern:
X(n) = B + X(n-1)*A (mod M)
X(n) = B + (B + X(n-2)*A)*A (mod M)
X(n) = B + (B + (B + X(n-3)*A)*A)*A (mod M)
//Aha! I see it now, and I can reduce it to a closed form:
X(n) = B + B*A + B*A*A + ... + B*A^(N-1) + S*A^N (mod M)
X(n) = S*A^N + B*SUM[i:0..n-1](A^i) (mod M)
X(n) = S*A^N + B*(A^N-1)/(A-1) (mod M)

最后一个等式可以相对快速地计算,虽然它的第二部分有点难以正确(因为除法不会以相同的方式分配加法和乘法)。

答案 7 :(得分:0)

据我所知,这里有两种基本算法:

  • 根据每个坐标的func(seed, coord)生成一个新的随机数
  • 从种子生成一个随机数,然后将其转换为坐标(类似rotate(x) + translate(y)或其他)

在第一种情况下,您遇到的问题是始终生成新的随机数,尽管这可能不像您担心的那样昂贵。

在第二种情况下,问题是您在转换操作期间可能会失去随机性。但是,在任何一种情况下,结果都是可重复的。