需要算法方面的帮助

6

我需要关于一个算法的帮助。我有随机生成的6位数字,例如:

123654 109431

大约有100万个数字按行保存在文件中。我必须根据下面所描述的规则对它们进行筛选。

拿一个数字,逐位与所有其他数字进行比较。如果一个数字的某一位的值比被比较的数字大1,则删除它。我用数字来展示这个过程。

我们的数字是:123456 将第一位增加1,那么这个数字就变成了:223456。删除文件中所有的223456。 将第二位增加1,数字变成:133456。删除文件中所有的133456,以此类推...

我可以按照上述描述完成这个任务,但我需要它“快速”完成。

所以有人能帮我吗?

谢谢。


6
当数字中有一个是9时会发生什么? - cdhowie
不仅要循环所有数字,还要等待答案的到来。 - Ahmet Kakıcı
我的答案比每次循环整个数组更快,因为它实际上是逐个数字处理输入的,只向前移动。但它需要大量的内存。 - cdhowie
内存不是问题,因为代码将在具有大量可用内存的服务器上运行。 - Élodie Petit
1
如果数字 x 导致删除 y,而 y 又导致删除 z,您需要澄清应该发生什么。 - Josephine
显示剩余3条评论
10个回答

5
首先,由于数量大约为1百万,建议在RAM中执行算法而不是在磁盘上执行。也就是说,首先将内容加载到数组中,然后修改数组,最后将结果粘贴回文件。
我建议使用以下算法 - 一个直截了当的算法。预先计算所有目标数字,例如223456、133456、124456、123556、123466、123457。现在遍历数组,如果数字不是其中任何一个,则将其写入另一个数组。或者,如果它是这些数字之一,则删除它(如果您的数据结构具有O(1)删除,则推荐此方法)。

我已经发了一种变体的方法,每个输入不需要进行六次比较... - egrunin

1

这个算法将在内存中保留许多数字,但它将一次处理一个数字文件,因此您实际上不需要一次性读取全部内容。 您只需要提供 IEnumerable<int> 即可进行操作。

    public static IEnumerable<int> FilterInts(IEnumerable<int> ints)
    {
        var removed = new HashSet<int>();

        foreach (var i in ints)
        {
            var iStr = i.ToString("000000").ToCharArray();

            for (int j = 0; j < iStr.Length; j++)
            {
                var c = iStr[j];

                if (c == '9')
                    iStr[j] = '0';
                else
                    iStr[j] = (char)(c + 1);

                removed.Add(int.Parse(new string(iStr)));

                iStr[j] = c;
            }

            if (!removed.Contains(i))
                yield return i;
        }
    }

您可以使用此方法从文件创建一个 IEnumerable<int>
    public static IEnumerable<int> ReadIntsFrom(string path)
    {
        using (var reader = File.OpenText(path))
        {
            string line;
            while ((line = reader.ReadLine()) != null)
                yield return int.Parse(line);
        }
    }

这个很好用。只是有一个小问题。int.Parse会吞掉前导零,但将过滤后的整数转换为字符串并用零填充到6个字符就可以解决问题了。感谢cdhowie的答案。 - Élodie Petit
哦,好发现。很高兴它对你有用。我会更新代码以造福他人。 - cdhowie

1

到目前为止,所有的建议都需要每个输入行进行六次比较,这是不必要的。由于数字以字符串形式出现,因此使用字符串比较。

从@Armen Tsirunyan的想法开始:

预先计算所有目标数字, 在本例中为223456、133456、124456、 123556、123466、123457。

但是,不要使用单个比较,而是将其转换为字符串:

 string arg = "223456 133456 124456 123556 123466 123457";

然后阅读输入(无论是从文件还是内存中)。 伪代码:

 foreach (string s in theBigListOfNumbers)
     if (arg.indexOf(s) == -1)
         print s;

每个输入行只有一个比较,没有字典、映射、迭代器等。

编辑添加:

在x86指令集处理器(不仅限于英特尔品牌),像这样的子字符串搜索非常快。例如,在字符串中搜索字符只需要一条机器指令。

我将不得不请其他人对替代架构进行评估。


2
这只是一个比较,但它必须比较长度为6的每个子字符串。由于您的“arg”字符串有41个字符,因此它必须对每个数字运行字符串比较(最多)41-6 = 35次。这是假设最差的实现。我不确定indexOf函数的运行时间是多少。 - thattolleyguy
这不是取决于indexOf的实现吗?英特尔处理器可能非常快,但不能保证在英特尔操作员上。你有关于这个的文档吗?如果听起来不友善,我很抱歉,我是一个相对新手,正在努力学习。 - thattolleyguy
即使使用x86操作码,这些字符串比较很可能仍然比仅进行6次比较慢(可以编写为仅使用一个条件跳转进行所有比较,因此速度会非常快)。 - Grizzly

1

将文件中的所有数字放入一个ArrayList中,然后:

将线程的数量作为数字的位数

在第一个线程中递增数字的第一位,在第二个线程中递增数字的第二位,然后与其余数字进行比较,

由于它将经过并行处理,因此速度会很快...


假设是多核CPU。 - cdhowie
这也可能在多个线程中多次找到目标数字。如果输入数字为123456,则第一个数字和第三个数字的线程将命中224456。 - Bill Carey
不,仔细阅读,它将在一次完成,将实际数字存储在int中,并在所有线程中使用不同的增量更改它以生成适当的数字。 - Genius
@Genius:它可以在任何设备上运行,但是在单核CPU上,它的速度不会比非线程方法更快。(除非CPU还支持超线程技术。) - cdhowie
我认为并行处理在这种情况下并不会有太大帮助:虽然您可以并行比较,但如何并行删除数字呢?这将需要锁定的序列化形式,这可能会比在一个线程中进行比较产生更多的开销。 - Grizzly

0

从文件中读取所有数字,并将它们存储在一个映射中,其中数字是键,布尔值是值,表示该值尚未被删除(True表示存在,False表示已删除)。

然后遍历你的键。对于每个键,将要从列表中删除的值的映射设置为False。

再次遍历列表,并获取所有值为True的键。这是剩余数字的列表。

public List<int> FilterNumbers(string fileName)
{
    StreamReader sr = File.OpenTest(fileName);
    string s = "";
    Dictionary<int, bool> numbers = new Dictionary<int, bool>();
    while((s = sr.ReadLine()) != null)
    {
        int number = Int32.Parse(s);
        numbers.Add(number,true);
    }
    foreach(int number in numbers.Keys)
    {
        if(numbers[number])
        {
            if(numbers.ContainsKey(100000+number))
                numbers[100000+number]=false;
            if(numbers.ContainsKey(10000+number))
                numbers[10000+number]=false;
            if(numbers.ContainsKey(1000+number))
                numbers[1000+number]=false;
            if(numbers.ContainsKey(100+number))
                numbers[100+number]=false;
            if(numbers.ContainsKey(10+number))
                numbers[10+number]=false;
            if(numbers.ContainsKey(1+number))
                numbers[1+number]=false;
        }
    }

    List<int> validNumbers = new List<int>();
    foreach(int number in numbers.Keys)
    {
        validNumbers.Add(number);
    }
    return validNumbers;
}

这个可能需要测试,因为我在这台电脑上没有C#编译器,而且我有点生疏。算法会占用一些内存,但它运行时间是线性的。

** 编辑 ** 每当其中一个数字是9时,这个代码就会遇到问题。我稍后会更新代码。


我觉得我可能误解了问题。我们是在寻找多个数字的多个变体还是单个数字的多个变体?例如,在完成123456之后,我们是否继续过滤文件中下一个数字的剩余数字,还是已经完成了? - thattolleyguy

0

首先,我会将所有数字读入数组中。

最后完成后,重新编写文件。


0

看起来你描述的规则是针对目标数字 abdcef,你想要找到所有包含 a+1、b+1、c+1、d+1、e+1 或 f+1 的数字。你可以通过循环遍历文件中的行,并将每个六位数字与目标数字进行比较,如果没有匹配的数字,则将该数字写入输出文件,时间复杂度为 O(n)。


0

这似乎是一个多维数组的潜在案例,可能还需要使用不安全的C#代码,以便您可以使用指针数学来迭代如此大量的数字。

我需要进一步挖掘,但如果您正在比较不连续的数字,我也可能会使用字典进行非线性查找。


0

这样怎么样。您逐个处理数字。数字将存储在哈希表NumbersOKNumbersNotOK中。

  1. 取一个数字
  2. 如果它不在NumbersNotOK中,则将其放入NumbersOK的哈希表中
  3. 获取单个数字增量的方差 - NumbersNotOK中的哈希值。
  4. 如果匹配任何方差,则删除所有NumbersOK成员。
  5. 重复从1开始,直到文件结束
  6. NumbersOK保存到文件中。

这样您只需一次通过列表。哈希表专门用于此类目的,速度非常快(没有昂贵的比较方法)。

该算法并不完整,因为它不能处理有些数字重复的情况,但可以通过一些调整来处理...


0

听起来还像是一个作业问题……在一百万个数字中找到最快的排序方法将是n log(n),即1000000log(1000000),这相当于将6个数字与一百万个数字进行比较。所以,直接比较将比排序和删除更快,因为在排序后你仍然需要进行比较以删除。除非,当然,我的计算完全错了。

另外还有一些其他想法。当您选择数字时,将其以十六进制而不是十进制读取。然后也许可以使用一些位运算符来帮助一些事情。 仍在思考如何使用它。如果有人发现此内容有用并能提供解决方案,我将进行更新。

编辑:目前在考虑使用格雷码。123456(我们的原始数字)和223456或133456只会差一个数字,并且格雷码转换器将快速捕捉它。现在已经很晚了,所以如果有人发现这个有用并能提供解决方案...


将生成的所有数字排序[123457,123466,123556,124456,133456,223456],然后将每个数字与第一个数字进行比较。如果不相等,则检查它是否小于第一个数字。如果是,则不要与下一个数字比较。我之所以说要排序,是因为9会滚动到0。我认为这是使用直接比较得到的最快速度,假设数字的分布符合标准分布。您是否希望数字以其他方式分布?这可能有助于加快计算速度。 - Ravindra Sane

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接