我开始学习C#并从事此类任务。我已经做到了,这是代码:
string y = "";
int count = 0;
string x = "";
string[] arr = new string[100000000];
for (int i = 0; i < 100000000; i++)
{
x = i.ToString() + x;
}
Console.WriteLine(x);
for (int j = 0; j < x.Length; j++)
{
y = x.Substring(j, 1);
switch (y)
{
case "1":
count ++;
break;
default:
break;
}
}
Console.WriteLine(count);
该代码在小范围(0到1000)的情况下可以正常工作,但是当在1亿的范围内运行时,它不会产生任何结果(我等待了一段时间,但没有输出),而且看起来我的代码效率不高。我的问题现在是这段代码中的问题是什么,以及是否有更好的解决方案来完成此任务。
我认为您最大的问题是字符串连接。C#新手犯的一个大错误是认为字符串是可变的,因为它们对外部观察者的行为如此。实际上,它们在内存中被视为不变的。每次x = i.ToString() + x
在循环中执行时,都会创建两个新的字符串,其中一个替换X保留的先前引用i.ToString()
,而旧的x
值则离开范围,但是直到GC可以访问它们时才从内存中删除它们。因此,该算法使运行时的内存管理层异常难以工作。
此外,您有一个1亿个元素的数组arr,占用了沙箱中的空间。这可能并没有真正让您放慢脚步,但肯定是浪费内存,因为它没有被使用。
“ switch”语句对于您要执行的操作来说太多了。完全相同的操作能够更简单地正是如此指定(和更少的底层操作): if(y=="1") count++;
。
最后,如果您希望使用大多数C风格的语言从字符串中提取单个字符,则只需将字符串视为一个字符数组(即):y = x[j];
将为您提供与相同的结果y=x.Substring(j,1)
,它将只是一个字符变量而不是一个字符的字符串,它应该快得多,因为您不会仔细研究String.Substring()方法中的逻辑。
取而代之的是,您只需按顺序对每个字符串进行计数并计算其中的1,然后将其加到总计中就可以得到相同的结果。如果通过这种方式进行调整,您现有的实现将可以正常工作,但是稍微使用Linq可以使代码更简洁(尽管不一定更快):
for(var i=1; i < 100000000; i++)
count += i.ToString().Count(c=>c=='1');
您甚至可以使用纯Linq解决方案将其单行处理:
var count = Enumerable.Range(1,99999999).Aggregate(0, (s, i) => s + i.ToString().Count(c=>c == '1'));
分解如下:
Enumerable.Range
产生一个“可枚举”(单次迭代序列),该整数从第一个参数开始一直递增,直到生成第二个参数的数字为止。Aggregate
基本上包装了一个复合循环;它采用“种子”值(0),将其作为匿名方法的第一个参数与源集合的第一个元素一起传递,然后采用该方法的结果,并将其与输入一起反馈到lambda语句中。下一个元素,依此类推,直到对每个元素都完成此操作。(s, i) => s + i.ToString().Count(c=>c == '1')
两个整数(“ s
eed”和“ i
nteger”),将整数变成一个字符串,计算该字符串中的“ 1”个字符的数量,然后将总数加到s
。Lambda的返回值为该总和。需要考虑的其他事项:
字符串解析还不错,但是整数数学通常要快得多。对于每个数字,请尝试除以10,然后以10为模。这将产生数字的每个数字,您可以将其与1进行比较,如果为true,则可以递增“ count”。这将花费更多的代码行,但它的工作速度仍比ToString()的每个数字都要快,因为这需要更多的计算步骤才能确定每个数字的字符值并将它们放在一起(并减少内存,因为它们是整数值)可以或多或少地就地进行操作,因此您不需要为每个数字使用字符串,而是需要为整个循环使用三个整数变量):
var count = 0;
for(var i=1; i<100000000; i++)
{
var j = i;
while(j > 0)
{
if(j % 10) == 1)
count++;
j /= 10;
}
}
本文收集自互联网,转载请注明来源。
如有侵权,请联系[email protected] 删除。
我来说两句