【问题标题】:Always Generate the Same Numbers to be Discarded Given a Percentage在给定百分比的情况下,始终生成要丢弃的相同数字
【发布时间】:2022-11-10 22:44:57
【问题描述】:

我正在处理一个很长的数字列表,比如 15 亿。我需要一种方法来指定我想保留的数字的百分比,其余的丢弃。现在我知道我可以使用随机数生成器来随机决定是否应该保留它,但问题是我需要保留/丢弃的数字始终相同。这意味着,如果我运行程序并决定丢弃索引 2、5 和 10,那么下次我运行程序时,它也必须丢弃 2、5 和 10。这个非常重要。

我也面临记忆问题。为了生成一个巨大的布尔列表来确定哪些数字被丢弃,哪些不是(例如,如果我们决定这样做),分析器说程序使用了大约 15gb 的内存,考虑到我还没有,这已经太多了另一个包含 15 亿个数字的列表。如果这很重要,这是我的代码:

        static bool[] GenerateShouldAddList(int totalCombos, decimal percentToAdd)
        {
            Random RNG = new Random();
            bool[] bools = new bool[totalCombos];
            int percent = (int)(percentToAdd * 100);

            for (int i = 0; i < totalCombos; i++)
            {
                int randNum = RNG.Next(0, 101);
                bools[i] = randNum < percent;
            }

            return bools;
        }

所以我在想,为了避免列出一个庞大的列表,有没有办法制作一个函数来接收索引号(比如索引 5364)、总数(15 亿)和你想要保留的百分比,然后返回给我是否应该添加该特定索引?如果我通过该函数一次运行每个索引,我应该只剩下我指定的数字百分比。最重要的是,这个函数应该总是为相同的索引返回相同的结果(如果 totalNumbers 和百分比没有改变)。我认为这是不可能的,但我也希望这里有人比我聪明得多。任何帮助表示赞赏!

【问题讨论】:

  • 尝试使用随机种子,例如Random RNG = new Random(12345); 它将生成随机但重复的序列
  • 尝试水库取样,在这种情况下您不必将整个数组加载到内存中,枚举就足够了:en.wikipedia.org/wiki/Reservoir_sampling

标签: c# algorithm random


【解决方案1】:

由于您有很多项目,我建议使用枚举,IEnumerable&lt;T&gt; 而不是数组 (这很可能不适合记忆)。另一个要求 - 可重复的选择 - 可以解决种子:我们创建new Random(seed) 并一次又一次地具有相同的序列:

static IEnumerable<bool> GenerateShouldAddList(int totalCombos, decimal percentToAdd) {
  Random RNG = new Random(789); // Whatever seed you like here 

  double threshold = percentToAdd / 100.0;

  for (int i = 0; i < totalCombos; ++i) 
    yield return RNG.NextDouble() < threshold;
}

如果你坚持拥有一个大批,您可以添加.ToArray(),例如

using System.Linq;

...

bool[] array = GenerateShouldAddList(10000, 5.0m).ToArray();

但我真的怀疑你是否应该这样做。

【讨论】:

  • 这完美!我不知道我可以简单地向 Random 添加一个种子。谢谢您的回答。然而,我选择了 TJ Rockefeller 的回答,因为它不需要我生成列表或枚举(我需要尽可能多地节省空间),并且它可以在并行 for 循环中工作,这也是我正在考虑的事情。
【解决方案2】:

听起来你想要一个函数,它接受一个索引和一个百分比,并且总是给出是否应该保留该索引的相同结果,但是以足够随机的方式。这可以通过使用散列算法来实现,这样输入总是以随机方式散列到相同的输出。我用 10,000 个索引测试了下面的内容,在 10% 时它保持 1006,在 50% 时它保持 4998,在 90% 时它保持 9006,所以保持的百分比非常接近要求的值,同时仍然是随机的。

using System.Security.Cryptography;

public static class ToKeepOrNotToKeep
{
    private static readonly MD5 _md5 = MD5.Create();

    public static bool AtIndex(int index, double percentToKeep)
    {
        var byteArray = BitConverter.GetBytes(index);
        var hash = _md5.ComputeHash(byteArray);
        //I know that the hash is 16 bytes, and here we are converting
        //only the first 8 bytes to a ulong, but it's still random and
        //should work just as well as if we used all 16 bytes for our
        //threshold test
        var number = BitConverter.ToUInt64(hash, 0);
        var threshold = ulong.MaxValue * percentToKeep;

        if (number <= threshold)
            return true;
        else
            return false;
    }
}

像这样运行加密哈希会产生一些开销,所以如果您担心性能,我在运行第 11 代 i7 11370H 的笔记本电脑上使用BenchmarkDotNet 对此过程进行了基准测试,平均运行时间为 220 ns。以您所说的数量,15 亿次操作仅运行此方法将消耗大约 5.5 分钟的 CPU 时间。如果您担心这 5.5 分钟,那么您可以找到更简单、更快的散列方法。

我很好奇通过切换到评论中建议的更简单的散列算法(如 FNV-1a)你会看到多少性能提升,所以我在下面实现了它

public static class ToKeepOrNotToKeep
{
    private static readonly MD5 _md5 = MD5.Create();

    public static bool AtIndex(int index, double percentToKeep)
    {
        var byteArray = BitConverter.GetBytes(index);
        var hash = getHash(byteArray);
        var threshold = ulong.MaxValue * percentToKeep;

        if (hash <= threshold)
            return true;
        else
            return false;
    }

    private ulong getHash(byte[] input)
    {
        unchecked
        {
            ulong hash = 14695981039346656037;

            foreach(var b in input)
            {
                hash ^= b;
                hash *= 1099511628211;
            }
        }
    }
}

以这个版本为基准,它比 MD5 密码散列快得多,平均每个方法调用大约需要 10 ns 运行,超过 15 亿次操作将是 15 秒而不是 5.5 分钟,但分布的随机性要小得多。为了粗略比较 MD5 哈希版本与 FNV1-a 版本的分布,这里是前 100 个索引,其中 1 表示保留,0 表示丢弃,运行率为 50%

MD5 1111011001110001001011001110110010000011000101110000001000100010100111110010111010011111000011000100

FNV-1a 1000011110000111100001111000011110000111100001111000011110000111100001111000011110000111100001111000

FNV-1a 版本感觉更具周期性,因此如果选择中的随机性对您很重要,那么 MD5 哈希方法的额外开销对您来说可能是值得的。如果分块是可以接受的并且您想要速度,那么 FNV-1a 版本会快得多。

【讨论】:

    【解决方案3】:

    您似乎想要一个接受索引和百分比值并返回布尔值的函数,无论您在 is_kept(index=50000, percent=50) 之前或之后调用 is_kept(index=2, percent=50) 并且不想存储任何内容,其结果都是相同的。

    为此,我会生成索引的哈希值,然后将其视为一个数字,除以最大哈希值并与百分比进行比较。这将给出类似随机的行为,而无需为状态或一长串标志分配任何内存。

    【讨论】:

    • 这实际上是一个很好的答案,但是如果不包含代码,对于像我这样没有使用哈希经验的人来说很难弄清楚如何实现。
    • 是的,但是这里还有很多关于“如何散列单个整数”的其他答案,例如stackoverflow.com/a/33816270,或者如果您不关心性能,您可以使用接受的答案
    猜你喜欢
    • 2012-03-11
    • 1970-01-01
    • 2011-09-26
    • 2020-03-03
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多