【问题标题】:Iterating over a list in pseudo-random order without storing a shuffled list以伪随机顺序迭代列表而不存储打乱列表
【发布时间】:2013-12-10 00:23:34
【问题描述】:

a game 中,我们使用一种称为“colour picking”的技术来选择单位。

这意味着每个可见单元都被赋予了独特的颜色。

这是一个为颜色选择绘制的场景示例:

由于某些用户可能使用 16 位显示器,因此这些颜色可以在 0..64K 范围内。

但是,如果我们给单位增加颜色,例如unit0 为 0,unitN 为 N,那么人类很难调试颜色。单位几乎无法区分。

我们希望赋予这些单元独特而独特的颜色。

我们目前正在使用二叉树 (C++ map) 以固定步长递增来存储使用过的颜色以检查冲突。这是低端硬件的性能问题。即使这是一个哈希表并且避免使用string,游戏帧中的临时内存分配也是不受欢迎的。因此,与其按原样优化代码,我更想发现是否有办法完全避免维护历史记录。

有没有办法以大步长或随机迭代数字 0..64K,以便使用大部分 64K 可能值,并避免使用已分配颜色的历史记录以避免冲突?

(屏幕上的可见单元不太可能超过 64K,我们不必处理这种情况!)

【问题讨论】:

  • 值 0 到 64K 可以使用 16 位整数存储,因此如果您要使用数组和 Fisher-Yates shuffle,您只需要一个 128K 数组和一个 16 位索引下一个单位的颜色。
  • 我不认为我理解这部分:“但是,如果我们给单位增加颜色,例如 unit0 是 0,unitN 是 N,那么这些颜色对于人类来说很难调试。单位实际上是无法区分。”你的意思是练习的重点是避免相似的颜色彼此靠近吗??
  • LFSR 怎么样?
  • 存在一个使用线性同余生成器的 hack,但您需要一些特殊的常量,以便它会准确地选择每个元素一次。不幸的是,我不记得黑客的名称,也找不到参考...

标签: c++ random


【解决方案1】:

我的尝试:

  1. 在您想要的范围附近选择一个prime number(64007 是一个不错的选择)。
  2. 找到此号码的primitive roots modulo p
  3. 选择一个“中等范围”的原始根(43062 43067 是一个很好的候选者)。

    class Sequence
    {
    public:
         uint32_t get() const {return state;}
         void advance() { state = (state * k)%p;}
         void reset() { state = k; }
    private:
         uint32_t state = 43067;
         const uint32_t k = 43067;
         const uint32_t p = 64007;
    };
    

这会将范围 [1, 64007) 中的所有数字以伪随机方式循环一次。

【讨论】:

  • 我非常喜欢这种方法!但我无法使用最简单的测试应用程序:gist.github.com/williame/0a91da1452890eb1b061 ?
  • 原来我复制并粘贴了错误的数字,43062 不是 64007 的原始根。43067 很好,我修正了答案。
  • 我希望看到一个将“期望范围”作为输入的通用实现。
【解决方案2】:

您可以简单地将 step_size 为可用颜色总数除以总单位数,然后使用 (unit_index * step_size) 作为每个单位的颜色吗?

【讨论】:

    【解决方案3】:

    在我看来,重要的是彼此靠近的单位之间的对比度足够高,即我会尝试想出一些方法来考虑单位的接近度。

    例如,您可以考虑单位的 X/Y 坐标,以便彼此靠近的坐标获得具有高对比度的颜色,低对比度仅用于足够远的单位。

    第一次尝试可能是有一个简单的数组a 256 色,这样a[n]a[n+1] 之间的对比度很大。然后,您可以使用单位的 X 和/或 Y 坐标模 256 作为数组的索引来选择单位的颜色。这样一来,对于相距至少 256 像素(或您可能使用的任何度量标准)的单位,您将获得重复使用的颜色,但对于彼此非常接近的单位,您将使用不同的颜色。

    【讨论】:

      【解决方案4】:

      我真的没有看到问题所在。正如我在 cmets 中所写,您只需要 128K 即可存储 [0..64K) 的排列,并且您不需要在主循环内进行任何分配。这是 C++11 中的有状态颜色存储(在旧版 C++ 中,使用 vectornew[]):

      class ColorPicker {
          std::array<uint16_t, 65536> colors;
          uint16_t idx;
      
        public:
          ColorPicker() : idx(0)
          {
              std::iota(colors.begin(), colors.end(), 0);
              std::random_shuffle(colors.begin(), colors.end());
          }
      
          uint16_t next_color()
          {
              return colors[idx++];
          }
      };
      

      您只需要其中一种结构。现在,每当您创建新单元时,请在 ColorPicker 上调用 next_color 并将其作为属性存储在单元中。

      此解决方案将循环显示颜色。如果不希望这样做,请在每次索引回零时执行 random_shuffle

      【讨论】:

        【解决方案5】:

        首先,使用二进制存储颜色状态(1-使用,0-未使用)。 2^16=65536(状态)。如果我们为一种颜色使用 1 位,则需要 65536/8=8192 字节。
        下一个问题是如何管理这些字节。我建议一个树结构:在这 8192 字节上,需要另一个 (8192/8=)1024 字节,如果低字节之一是 ALL,则这些高字节中的每一位代表 8192 字节中的一个字节1,其高位为 1。
        这条规则可以向上和向上扩展:8192 -> 1024 -> 128 ...最后,到1个字节(虽然没有完全使用)。
        使用这种结构,可以多次生成一个0..7的随机数,从根字节开始,如果位为1,再试一次;如果它为 0,向下到低字节,重复这些操作直到到达最低字节。
        此外,您可以在一个数组中构建此树:就像 heapsort 中的堆一样。 (有一些空单位)。

        APPEND:一个颜色需要一个int16,一旦下降到一个低字节,你会得到一个三位二进制数,从左到右将它们附加到颜色数:int16。 (根字节只代表2种状态,只产生1位二进制数,形式为111111??。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2016-03-11
          • 1970-01-01
          • 2017-02-16
          • 2021-08-06
          • 2018-08-17
          相关资源
          最近更新 更多