【问题标题】:How can I best generate a static array of random number on demand?如何最好地按需生成随机数的静态数组?
【发布时间】:2011-03-09 06:21:19
【问题描述】:

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

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

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])

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

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

【问题讨论】:

  • 随机性的质量对您来说很重要吗?那么,数字具有良好的“随机”分布是否重要?
  • 我希望随机数具有与静态生成数组类似的分布(构造函数中具有已知维度的大 for 循环)。基本上,最好(尽管显然不可能)的解决方案是在创建对象时生成一个无限的随机数矩阵,然后简单地返回所要求的任何值。
  • 这将是艰难的。我不知道允许您随机访问随机序列的随机数生成器(虽然可能有一个),但伪随机数生成器会生成序列中的数字。听起来您想要做的是根据矩阵中的索引从序列中选择一个随机数,您所看到的问题是您需要预先生成整个序列。我能想到的最好的选择是选择一个弱prng,它根据索引和种子生成一个数字,分布不会很好......
  • “类似”? - 我从来没有听说过那个分布。 :) 但是您可以通过应用一些函数(在某些地方改变密度)来转换一些分布。

标签: c# algorithm random


【解决方案1】:

您所说的通常称为“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 坐标返回一个介于 -1.0 和 +1.0 之间的数字(以及一个硬编码的种子,您可以在应用程序开始时随机更改或保持原样)。

本文的其余部分是关于对这些数字进行插值,但根据您希望这些数字的随机程度,您可以将它们保持原样。请注意,这些数字将是完全随机的。如果您改为应用余弦插值器并使用每 5-6 个索引生成的噪声,在两者之间进行插值,您将获得高度图数据(这就是我使用它的目的)。跳过它以获得完全随机的数据。

【讨论】:

  • 是的,我正在尝试生成 Perlin 噪声。我从中看到的例子都是在构造过程中在数组中生成了噪声函数。这些都是自定义哈希函数中的幻数吗?
  • @Blindy - 你的函数并不完全是通常所说的 Perlin Noise。 Perlin Noise 涉及以不同的波长和幅度对其进行多次迭代。但它确实给了提问者他想要的东西。
  • @Blindy - 以贾斯汀所说的为基础 - 就像你说的那样,“这些数字将完全随机。” Perlin Noise 通常会对其应用过滤器,使其不是完全随机的,因此它的行为类似于高度图或烟雾分布或类似的东西。
  • Perlin 噪声显示附近点之间存在很多相关性。这是您想要的吗,Daniel,还是您希望每个点都完全独立于其邻居?
  • @Justin - 抱歉,我刚刚意识到您是回答我之前问题的同一个人。没必要把你指向那里... facepalm
【解决方案2】:

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

附:您可以通过制作惰性矩阵块来改进使用独立生成器 (Random(seed + index)) 的方法。

【讨论】:

  • 相似整数的hash 有多随机?我正在考虑使用惰性块来改进Random(seed + index) 方法,但我仍然认为必须有更好的方法。
  • @Daniel,好的散列函数应该尝试产生uniform-distributed 散列。因此,将它们用于非随机数据应该会产生均匀分布的值。之后,您可以将其转换为其他一些发行版。
【解决方案3】:

我认为,在大多数情况下,实例化一个由(x 坐标、y 坐标、LazyRandomMatrix 种子)的确定性哈希播种的新 Random 对象的第一个想法可能是合理的。一般来说,在托管堆上创建大量小对象是 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());
    }
}

【讨论】:

  • 甚至,新的随机(_seed * x ^ y)。
  • 这会比简单地散列seed ^ x ^ y 产生更好的分布吗? (seed * x ^ y 似乎如果我的坐标足够高,它可能会溢出......)
  • @Daniel:只要您不使用检查数学(请参阅检查关键字或 C# 编译器的检查关键字),溢出应该没问题,因为结果将只是适合的低位.溢出的位将被忽略。
  • @DonaldRay:我使用 2 个 Random 对象只是因为它似乎更容易避免不均匀分布或奇怪的模式。例如,当 x == 0 时,(_seed * x ^ y) 不会随 _seed 变化。也就是说,无论 _seed 是什么,lazy[0, 1] 都会产生相同的结果。
  • 大多数 RNG 为相似的种子产生相似的输出,所以这是个坏主意。
【解决方案4】:

这是一个基于 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】:

PRNGs can be built out of hash functions.
这就是例如MS Research 在 GPU 上并行处理随机数生成 with MD5 others with TEA
(事实上​​,PRNG 可以被认为是来自 (seed, state) => 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;
    }

}

【讨论】:

  • 负值会弄乱 MurmurHash2 吗?如果我想让它带两个浮点数怎么办?
  • @Daniel:不,他们不会。上面到 Murmur 2 的链接显示它需要一个字节序列。由于所有内容都可以转换为字节序列,因此原始内容可以接受所有内容。我只是尝试根据您的情况简化原件。可以在 Murmur 的维基百科页面上找到 C# 实现。
【解决方案6】:

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

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

类似这样的:

  • (0,0) = 种子
  • (x+1,0) = 后继(值(x,0))
  • (x,y+1) = 后继(值(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

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

【讨论】:

  • 我只是建议使用准蒙特卡洛,但我改变了主意。但是看到你的帖子,我认为准随机是要走的路。
  • 一个很好的建议,但正如我所说,它们应该“独立且按需”生成。所以如果我需要得到值(100,100),我不想在得到我想要的那个之前计算出 200 个后继。
  • @Daniel Rasmussen:如果您确定要为相同的种子值生成相同的矩阵,并且不希望这些值纯粹基于索引,我相信没有别的办法计算给定索引对的所有中间值。
  • 是的,这是在随机世界中移动角色的好主意(即一次移动一个单元格时)。但是您仍然必须为随机访问的值生成序列。
  • 我假设这里的平局是它会提供比只使用索引更平等的分布?平心而论,这可能工作得很好,因为我正在做的正是 ony 所说的——一个在随机世界上移动的角色,而且很可能不必跳得太远而成为问题。
【解决方案7】:

您需要一个随机访问元素的随机数生成器,而不是顺序访问。 (请注意,您可以将两个坐标减少为一个索引,即通过计算 i = x + (y

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)

最后一个方程可以相对快速地计算出来,尽管它的第二部分要正确有点棘手(因为除法不像加法和乘法那样分布在 mod 上)。

【讨论】:

  • +1 对线性生成器的出色解释并将其扩展为非递归形式。
【解决方案8】:

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

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

在第一种情况下,您总是会遇到生成新随机数的问题,尽管这可能没有您担心的那么昂贵。

在第二种情况下,问题是您在转换操作期间可能会丢失随机性。然而,无论哪种情况,结果都是可重现的。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2010-09-24
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-10-10
    • 2021-05-15
    • 2011-02-01
    相关资源
    最近更新 更多