【问题标题】:Quickest algorithm for identifying pairs in collection of string识别字符串集合中对的最快算法
【发布时间】:2018-07-15 19:55:58
【问题描述】:

我正在寻找最快的算法:

GOAL:输出在一行上找到的对出现的总数。单个元素可以在任何给定行上以任何顺序排列。

输入:

a;b;c;d
a;e;f;g
a;b;f;h

输出

a;b = 2
a;c = 1
a;d = 1
a;e = 1
a;f = 2
a;g = 1
b;c = 1
b;d = 1

我正在用 C# 编程,我有一个嵌套的 for 循环,添加了一个常见的类型字典,其中 string 类似于 a;b,当找到一个出现时,它会添加到现有的 int 计数中或在计数 = 0。

注意这一点:

a;b = 1
b;a = 1

应该简化为:

a;b = 1

我愿意使用其他语言,输出是一个纯文本文件,我将它输入到 Gephi 可视化工具中。

奖励:非常有兴趣知道这个特定算法的名称(如果有的话)。可以肯定的是。

String[] data = File.ReadAllLines(@"C:\input.txt");
Dictionary<string, int> ress = new Dictionary<string, int>();

foreach (var line in data)
{
    string[] outStrings = line.Split(';');

    for (int i = 0; i < outStrings.Count(); i++)
    {
        for (int y = 0; y < outStrings.Count(); y++)
        {
            if (outStrings[i] != outStrings[y])
            {
                try
                {
                    if (ress.Any(x => x.Key == outStrings[i] + ";" + outStrings[y]))
                    {
                        ress[outStrings[i] + ";" + outStrings[y]] += 1;
                    }
                    else
                    {
                        ress.Add(outStrings[i] + ";" + outStrings[y], 0);
                    }
                }
                catch (Exception)
                {

                }
            }
        }
    }
}

foreach (var val in ress)
{
    Console.WriteLine(val.Key + "----" + val.Value);
}

【问题讨论】:

  • 我刚刚发布了当前在我的笔记本电脑上运行的代码 :-)
  • 这看起来像association rule learning,尽管没有假设您只关心超过某个阈值的关联。就像您只对一起出现超过 2 次的配对感兴趣。这个假设可以给你带来一些效率。

标签: c# algorithm performance


【解决方案1】:

我认为你的内循环应该从i + 1 开始,而不是再次从0 开始,外循环应该只运行到Length - 1,因为最后一项将在内循环上进行比较。此外,当您添加新项目时,您应该添加值 1,而不是 0(因为我们添加它的全部原因是因为我们找到了一个)。

您也可以只将键存储到一个字符串中,而不是在比较和分配期间进行多次连接,并且您可以使用ContainsKey 方法来确定键是否已经存在。

此外,您可能需要考虑避免使用空的 catch 块,除非您真的确定自己不在乎是否或出了什么问题。如果我期待一个异常并且知道如何处理它,那么我会捕获该异常,否则我只会让它在堆栈中冒泡。

这是一种修改代码以查找所有对及其计数的方法:

更新

我添加了一个检查来确保“pair”键总是被排序,这样“b;a”就变成了“a;b”。这不是您的示例数据中的问题,但我扩展了数据以包含像 b;a;a;b;a;b;a; 这样的行。我还在Split 方法中添加了StringSplitOptions.RemoveEmptyEntries,以处理行以; 开头或结尾的情况(否则null 值会导致一对";a")。

private static void Main()
{
    var data = File.ReadAllLines(@"f:\public\temp\temp.txt");
    var pairCount = new Dictionary<string, int>();

    foreach (var line in data)
    {
        var lineItems = line.Split(new[] {';'}, StringSplitOptions.RemoveEmptyEntries);

        for (var outer = 0; outer < lineItems.Length - 1; outer++)
        {
            for (var inner = outer + 1; inner < lineItems.Length; inner++)
            {
                var outerComparedToInner = string.Compare(lineItems[outer], 
                    lineItems[inner], StringComparison.Ordinal);

                // If both items are the same character, ignore them and keep looping
                if (outerComparedToInner == 0) continue;

                // Create the pair such that the lower of the two 
                // values is first, so that "b;a" becomes "a;b"
                var thisPair = outerComparedToInner < 0
                    ? $"{lineItems[outer]};{lineItems[inner]}"
                    : $"{lineItems[inner]};{lineItems[outer]}";

                if (pairCount.ContainsKey(thisPair))
                {
                    pairCount[thisPair]++;
                }
                else
                {
                    pairCount.Add(thisPair, 1);
                }
            }
        }
    }

    Console.WriteLine("Pair\tCount\n----\t-----");

    foreach (var val in pairCount.OrderBy(i => i.Key))
    {
        Console.WriteLine($"{val.Key}\t{val.Value}");
    }

    Console.Write("\nDone!\nPress any key to exit...");
    Console.ReadKey();

}

输出

给定一个包含样本数据的文件,输出为:

【讨论】:

  • 我需要从中吸取教训。 LINQ 很慢。我想象它会有一些超级棒的幕后优化。它没有。一旦我的 LINQ 解决方案被证明慢了 100 倍,我就放弃了,并开始尝试调整您的答案以使其更快。任何事情都让它变慢了。
  • 我猜有时“老式”循环是最快的。不过,我仍然相信我错过了一些优化,所以我很高兴你在尝试!这是最好的学习方式。而且我也不喜欢密钥生成代码。即使它更慢,一些自定义排序 Linq 的东西看起来会更好。 :)
  • 感谢您,非常有帮助。关于算法名称可能是什么的任何想法?想知道是否有更好的方法,因为我有大量数据。
  • 不,我不确定。 This page 描述了排列和组合,可能会有所帮助。
【解决方案2】:

@mrmcgreg,终于在将实现更改为ECLAT algorythm 之后,一切都在几秒钟内而不是几小时内运行。

基本上,对于每个唯一标签,跟踪找到这些标签的行号,并简单地通过组合对将这对数字列表相交以获得计数。

 Dictionary<string, List<int>> uniqueTagList = new Dictionary<string, List<int>>();


            foreach (var uniqueTag in uniquetags)
            {
                List<int> lineNumbers = new List<int>();
                foreach (var item in data.Select((value, i) => new { i, value }))
                {
                    var value = item.value;
                    var index = item.i;

                    //split data into tags
                    var tags = item.ToString().Split(new[] { ';' }, StringSplitOptions.RemoveEmptyEntries);

                    foreach (var tag in tags)
                    {
                        if (uniqueTag == tag)
                        {
                            lineNumbers.Add(index);
                        }
                    } 
                }

                //remove all having support threshold.
                if (lineNumbers.Count > 5)
                {
                    uniqueTagList.Add(uniqueTag, lineNumbers);
                }  
            }

【讨论】:

    猜你喜欢
    • 2017-05-10
    • 2014-02-18
    • 1970-01-01
    • 2019-01-24
    • 2021-06-08
    • 1970-01-01
    • 2019-07-29
    • 2011-07-28
    • 2015-10-27
    相关资源
    最近更新 更多