将双列表转换为分组字符串

时间:2016-12-03 00:55:26

标签: java c# algorithm performance testing

程序输入一个双精度列表,输出需要是一个字符串,其中包含按值分组的列表值。如果列表值相等,则将对它们进行分组。  就像是:     输入9,77,5,5,31 =>输出9 77 2 * 5 31

我在C#中创建了一个算法(在Java中我认为几乎是相同的),但我不确定它是否可以改进其速度或代码质量,或者它是否有一些我看不到的错误。该算法还有一些输入,输出示例如下。

{{1}}

如果您发现了一些错误或看到可以做的一些改进,请告诉我。

此外,如果你知道这个要求的另一个实现,请添加它,以便可以完成算法之间的结果,速度,代码质量的比较......并找到处理这个问题的最佳方法。

2 个答案:

答案 0 :(得分:3)

由于要求只对连续相等的值进行分组,因此另一个答案中提到的Dictionary和LINQ GroupBy方法不适用,因为它们会产生不正确的输入结果序列如1,2,1。此外,没有标准的LINQ方法可以进行此类分组(最终Aggregate方法除外),但它只不过是低效的for / foreach循环等效方法。

很快,您的算法最适合此类任务。但实施不是。

主要的瓶颈是Peroxy提到的字符串连接,使用StringBuilder类可以很容易地修复(在另一个答案中也提到)。一旦你这样做,性能将会很好。

我在实现中看到的另一个问题是使用特殊值(double.MinValue),重复的角落案例检查,在体内减少for循环变量等等。所以虽然它可能有用但我不喜欢直接看到一个错误,很难遵循算法逻辑并发现潜在的错误只是阅读实现。算法本身很简单,我会这样实现:

static string ListAsString(List<double> input)
{
    var sb = new StringBuilder();
    for (int i = 0; i < input.Count; )
    {
        var value = input[i];
        int count = 1;
        while (++i < input.Count && input[i] == value)
            count++;
        if (sb.Length > 0) sb.Append(' ');
        if (count > 1) sb.Append(count).Append('*');
        sb.Append(value);
    }
    return sb.ToString();
}

哪个IMO更容易理解。请注意,没有重复的代码,没有特殊值,循环变量i前进仅在外部循环体内的一个位置完成。同样,这与性能无关(由StringBuilder用法提供),但只是可读性,冗余消除和不易出错。

答案 1 :(得分:2)

就个人而言,我认为这里有Dictionary使用的巨大潜力,这是我用字典实现的快速解决方案:

var input = new List<double> { 9, 77, 5, 5, 31 };
var dict = new Dictionary<double, int>();
var listAsString = new StringBuilder();

foreach (var item in input)
{
    if (dict.ContainsKey(item))
        dict[item]++;
    else
        dict[item] = 1;
}

foreach (var item in dict)
{
    listAsString.Append(item.Value > 1 ? $"{item.Value}*{item.Key} " : $"{item.Key} ");
}

Console.WriteLine(listAsString);

如果您想要一种无效的LINQ单线解决方案:

string result = string.Join(" ", input.GroupBy(i => i)
                                       .Select(x => 
                                       x.Count() > 1 ? 
                                       $"{x.Count()}*{x.Key} " : 
                                       $"{x.Key} "));

但是,我相信你的方法编写得很好,虽然比字典的方法稍差,但你的解决方案的主要缺点是你在构建最终字符串时使用字符串,你绝对应该使用{ {1}},我在您的方法中引入了StringBuilder并对这三种方法进行了比较:

StringBuilder

您的解决方案始终是最快的,如果您想加快速度,保留解决方案,但将其更改为使用Dictionary | Your method | GroupBy method ------------------------------------------------ 2 ms | 0 ms | 5 ms n=3 0 ms | 0 ms | 0 ms n=6 0 ms | 0 ms | 0 ms n=12 0 ms | 0 ms | 0 ms n=24 0 ms | 0 ms | 0 ms n=48 0 ms | 0 ms | 0 ms n=96 0 ms | 0 ms | 0 ms n=192 0 ms | 0 ms | 0 ms n=384 0 ms | 0 ms | 0 ms n=768 0 ms | 0 ms | 0 ms n=1536 1 ms | 0 ms | 1 ms n=3072 3 ms | 2 ms | 3 ms n=6144 5 ms | 4 ms | 6 ms n=12288 8 ms | 7 ms | 14 ms n=24576 14 ms | 13 ms | 25 ms n=49152 31 ms | 32 ms | 66 ms n=98304 80 ms | 59 ms | 146 ms n=196608 149 ms | 123 ms | 294 ms n=393216 246 ms | 218 ms | 504 ms n=786432 483 ms | 428 ms | 1040 ms n=1572864 999 ms | 873 ms | 2070 ms n=3145728 1995 ms | 1784 ms | 3950 ms n=6291456 ,请使用StringBuilder代替listAsString.Append(currentElem + " ")

如果您只对具有listAsString += currentElem + " "的集合进行操作,则可以使用

GroupBy,如果您希望使用可读性超过速度,请使用n < 1000解决方案。