[英]Convert a double list to a grouped string
该程序的输入为双精度列表,输出为包含按值分组的列表值的字符串。 如果列表值相等,则将它们分组。 像这样的东西:输入9,77,5,5,31 =>输出9 77 2 * 5 31
为此,我在C#中创建了一个算法(在Java中,我认为几乎是相同的),但是我不确定在速度或代码质量方面是否可以改进它,或者是否存在一些我看不到的错误。 下面还有另外一些输入,输出示例的算法。
List<double> input = new List<double> { 11, 32, 32, 43}; // output 11 2*32 43
//List<double> input = new List<double> { 11, 11, 43, 43 }; // output 2*11 2*43
//List<double> input = new List<double> { 10, 11, 12, 13, 14, 15, 16 }; // output 10 11 12 13 14 15 16
//List<double> input = new List<double> { 11, 11, 11, 11, 11 }; // output 5 * 11
//List<double> input = new List<double> { 11, 11, 32, 22, 22, 22, 4, 10, 10 }; // output 2*11 32 3*22 4 2*10
string listAsString = string.Empty;
double nextElem = double.MinValue;
for (int i = 0; i < input.Count; i++)
{
double currentElem = input[i];
if (i + 1 < input.Count)
{
nextElem = input[i + 1];
}
int equalCount = 0;
while (currentElem.Equals(nextElem) && i < input.Count)
{
equalCount++;
i++;
currentElem = nextElem;
if (i < input.Count)
{
nextElem = input[i];
}
}
if (equalCount < 2)
{
listAsString += currentElem + " ";
}
else
{
listAsString += equalCount + "*" + currentElem + " ";
i--;
}
}
Console.WriteLine(listAsString);
如果您发现一些错误或看到可以完成的改进,请告诉我。
另外,如果您知道此要求的另一种实现,请添加它,以便可以对算法之间的结果,速度,代码质量进行比较...并找到处理此问题的最佳方法。
由于要求仅对连续的相等值进行分组,因此另一个答案中提到的Dictionary
和LINQ GroupBy
方法不适用,因为它们会对诸如1,2,1
类的输入序列产生不正确的结果。 同样,也没有标准的LINQ方法来进行这种分组(最终的Aggregate
方法除外,但for
/ foreach
循环等效项而言,效率不高)。
不久,您的算法最适合此类任务。 但是执行不是。
主要的瓶颈是Peroxy提到的字符串连接,该字符串连接(在其他答案中也提到了)可以通过使用StringBuilder
类轻松修复。 一旦执行此操作,性能将很好。
另一个问题我在执行看到的是特殊值(使用double.MinValue
),重复的极端情况的检查,递减for
身体等内部循环变量所以,虽然它可能工作,我不直接看到一个bug,它是一种很难理解算法逻辑并在阅读实现时发现潜在的错误。 该算法本身非常简单,我可以通过以下方式实现:
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
用法提供),而仅仅是可读性,冗余消除和较少的错误倾向。
就个人而言,我在这里发现使用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} "));
但是,我相信您的方法写得很好,虽然比字典的可读性差,但是解决方案的主要缺点是,在构建最终字符串时您使用的是字符串,您肯定应该使用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 + " "
。
如果仅对n < 1000
集合进行操作,则可以使用GroupBy
如果您希望在速度上提高可读性,请使用Dictionary
解决方案。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.