在.NET中将int的数字分成数组的最快方法?

我想将一个整数的数字(比如12345)分成一个字节数组{1,2,3,4,5},但我想要最有效的方法来做到这一点,因为我的程序做了数百万次。

有什么建议? 谢谢。

怎么样:

public static int[] ConvertToArrayOfDigits(int value) { int size = DetermineDigitCount(value); int[] digits = new int[size]; for (int index = size - 1; index >= 0; index--) { digits[index] = value % 10; value = value / 10; } return digits; } private static int DetermineDigitCount(int x) { // This bit could be optimised with a binary search return x < 10 ? 1 : x < 100 ? 2 : x < 1000 ? 3 : x < 10000 ? 4 : x < 100000 ? 5 : x < 1000000 ? 6 : x < 10000000 ? 7 : x < 100000000 ? 8 : x < 1000000000 ? 9 : 10; } 

请注意,这不会应付负数...你需要它吗?

编辑:这是一个版本,按照埃里克的建议记忆10000以下的结果。 如果您绝对可以保证不会更改返回数组的内容,则可以删除Clone调用。 它还有一个方便的属性,减少检查的数量,以确定“大”数字的长度 - 而小数字只会通过该代码一次:)

 private static readonly int[][] memoizedResults = new int[10000][]; public static int[] ConvertToArrayOfDigits(int value) { if (value < 10000) { int[] memoized = memoizedResults[value]; if (memoized == null) { memoized = ConvertSmall(value); memoizedResults[value] = memoized; } return (int[]) memoized.Clone(); } // We know that value >= 10000 int size = value < 100000 ? 5 : value < 1000000 ? 6 : value < 10000000 ? 7 : value < 100000000 ? 8 : value < 1000000000 ? 9 : 10; return ConvertWithSize(value, size); } private static int[] ConvertSmall(int value) { // We know that value < 10000 int size = value < 10 ? 1 : value < 100 ? 2 : value < 1000 ? 3 : 4; return ConvertWithSize(value, size); } private static int[] ConvertWithSize(int value, int size) { int[] digits = new int[size]; for (int index = size - 1; index >= 0; index--) { digits[index] = value % 10; value = value / 10; } return digits; } 

请注意,此刻不会尝试保持线程安全。 您可能需要添加内存屏障,以确保在单个结果中的写入可见之前,对已记忆结果的写入不可见。 除非我绝对必须这样做,否则我已经放弃了尝试推理这些事情。 我相信你可以通过努力使其无锁,但如果你真的需要,你应该让一个非常聪明的人这样做。

编辑:我刚刚意识到“大”的情况可以利用“小”的情况 - 将大数字分成两个小的并使用记忆结果。 我会在晚餐后给你一个去,写一个基准......

编辑:好的,准备好了大量的代码? 我意识到,至少对于均匀随机数,你会比小数字更频繁地得到“大”数字 - 所以你需要优化它。 当然,这可能不是真实数据的情况,但无论如何......这意味着我现在以相反的顺序进行尺寸测试,希望首先是大数字。

我有原始代码的基准,简单的memoization,然后是极度展开的代码。

结果(以毫秒为单位):

 Simple: 3168 SimpleMemo: 3061 UnrolledMemo: 1204 

码:

 using System; using System.Diagnostics; class DigitSplitting { static void Main() { Test(Simple); Test(SimpleMemo); Test(UnrolledMemo); } const int Iterations = 10000000; static void Test(Func candidate) { Random rng = new Random(0); Stopwatch sw = Stopwatch.StartNew(); for (int i = 0; i < Iterations; i++) { candidate(rng.Next()); } sw.Stop(); Console.WriteLine("{0}: {1}", candidate.Method.Name, (int) sw.ElapsedMilliseconds); } #region Simple static int[] Simple(int value) { int size = DetermineDigitCount(value); int[] digits = new int[size]; for (int index = size - 1; index >= 0; index--) { digits[index] = value % 10; value = value / 10; } return digits; } private static int DetermineDigitCount(int x) { // This bit could be optimised with a binary search return x < 10 ? 1 : x < 100 ? 2 : x < 1000 ? 3 : x < 10000 ? 4 : x < 100000 ? 5 : x < 1000000 ? 6 : x < 10000000 ? 7 : x < 100000000 ? 8 : x < 1000000000 ? 9 : 10; } #endregion Simple #region SimpleMemo private static readonly int[][] memoizedResults = new int[10000][]; public static int[] SimpleMemo(int value) { if (value < 10000) { int[] memoized = memoizedResults[value]; if (memoized == null) { memoized = ConvertSmall(value); memoizedResults[value] = memoized; } return (int[]) memoized.Clone(); } // We know that value >= 10000 int size = value >= 1000000000 ? 10 : value >= 100000000 ? 9 : value >= 10000000 ? 8 : value >= 1000000 ? 7 : value >= 100000 ? 6 : 5; return ConvertWithSize(value, size); } private static int[] ConvertSmall(int value) { // We know that value < 10000 return value >= 1000 ? new[] { value / 1000, (value / 100) % 10, (value / 10) % 10, value % 10 } : value >= 100 ? new[] { value / 100, (value / 10) % 10, value % 10 } : value >= 10 ? new[] { value / 10, value % 10 } : new int[] { value }; } private static int[] ConvertWithSize(int value, int size) { int[] digits = new int[size]; for (int index = size - 1; index >= 0; index--) { digits[index] = value % 10; value = value / 10; } return digits; } #endregion #region UnrolledMemo private static readonly int[][] memoizedResults2 = new int[10000][]; private static readonly int[][] memoizedResults3 = new int[10000][]; static int[] UnrolledMemo(int value) { if (value < 10000) { return (int[]) UnclonedConvertSmall(value).Clone(); } if (value >= 1000000000) { int[] ret = new int[10]; int firstChunk = value / 100000000; ret[0] = firstChunk / 10; ret[1] = firstChunk % 10; value -= firstChunk * 100000000; int[] secondChunk = ConvertSize4(value / 10000); int[] thirdChunk = ConvertSize4(value % 10000); ret[2] = secondChunk[0]; ret[3] = secondChunk[1]; ret[4] = secondChunk[2]; ret[5] = secondChunk[3]; ret[6] = thirdChunk[0]; ret[7] = thirdChunk[1]; ret[8] = thirdChunk[2]; ret[9] = thirdChunk[3]; return ret; } else if (value >= 100000000) { int[] ret = new int[9]; int firstChunk = value / 100000000; ret[0] = firstChunk; value -= firstChunk * 100000000; int[] secondChunk = ConvertSize4(value / 10000); int[] thirdChunk = ConvertSize4(value % 10000); ret[1] = secondChunk[0]; ret[2] = secondChunk[1]; ret[3] = secondChunk[2]; ret[4] = secondChunk[3]; ret[5] = thirdChunk[0]; ret[6] = thirdChunk[1]; ret[7] = thirdChunk[2]; ret[8] = thirdChunk[3]; return ret; } else if (value >= 10000000) { int[] ret = new int[8]; int[] firstChunk = ConvertSize4(value / 10000); int[] secondChunk = ConvertSize4(value % 10000); ret[0] = firstChunk[0]; ret[1] = firstChunk[0]; ret[2] = firstChunk[0]; ret[3] = firstChunk[0]; ret[4] = secondChunk[0]; ret[5] = secondChunk[1]; ret[6] = secondChunk[2]; ret[7] = secondChunk[3]; return ret; } else if (value >= 1000000) { int[] ret = new int[7]; int[] firstChunk = ConvertSize4(value / 10000); int[] secondChunk = ConvertSize4(value % 10000); ret[0] = firstChunk[1]; ret[1] = firstChunk[2]; ret[2] = firstChunk[3]; ret[3] = secondChunk[0]; ret[4] = secondChunk[1]; ret[5] = secondChunk[2]; ret[6] = secondChunk[3]; return ret; } else if (value >= 100000) { int[] ret = new int[6]; int[] firstChunk = ConvertSize4(value / 10000); int[] secondChunk = ConvertSize4(value % 10000); ret[0] = firstChunk[2]; ret[1] = firstChunk[3]; ret[2] = secondChunk[0]; ret[3] = secondChunk[1]; ret[4] = secondChunk[2]; ret[5] = secondChunk[3]; return ret; } else { int[] ret = new int[5]; int[] chunk = ConvertSize4(value % 10000); ret[0] = value / 10000; ret[1] = chunk[0]; ret[2] = chunk[1]; ret[3] = chunk[2]; ret[4] = chunk[3]; return ret; } } private static int[] UnclonedConvertSmall(int value) { int[] ret = memoizedResults2[value]; if (ret == null) { ret = value >= 1000 ? new[] { value / 1000, (value / 100) % 10, (value / 10) % 10, value % 10 } : value >= 100 ? new[] { value / 100, (value / 10) % 10, value % 10 } : value >= 10 ? new[] { value / 10, value % 10 } : new int[] { value }; memoizedResults2[value] = ret; } return ret; } private static int[] ConvertSize4(int value) { int[] ret = memoizedResults3[value]; if (ret == null) { ret = new[] { value / 1000, (value / 100) % 10, (value / 10) % 10, value % 10 }; memoizedResults3[value] = ret; } return ret; } #endregion UnrolledMemo } 

1 + Math.Log10(num)将给出没有任何搜索/循环的位数:

 public static byte[] Digits(int num) { int nDigits = 1 + Convert.ToInt32(Math.Floor(Math.Log10(num))); byte[] digits = new byte[nDigits]; int index = nDigits - 1; while (num > 0) { byte digit = (byte) (num % 10); digits[index] = digit; num = num / 10; index = index - 1; } return digits; } 

编辑:可能更漂亮:

 public static byte[] Digits(int num) { int nDigits = 1 + Convert.ToInt32(Math.Floor(Math.Log10(num))); byte[] digits = new byte[nDigits]; for(int i = nDigits - 1; i != 0; i--) { digits[i] = (byte)(num % 10); num = num / 10; } return digits; } 

将整数转换为字符串,然后使用String.Chars []

数百万次并不是那么多。

 // input: int num >= 0 List digits = new List(); while (num > 0) { byte digit = (byte) (num % 10); digits.Insert(0, digit); // Insert to preserve order num = num / 10; } // if you really want it as an array byte[] bytedata = digits.ToArray(); 

请注意,如果将字节更改为sbyte并测试num != 0 ,则可以更改此值以应对负数。

‘威尔’与’有’吗? 在编写,分析后,我非常喜欢优化代码,并且它已经被确定为瓶颈。

只是为了好玩,这里是一种使用一个C#语句分隔所有数字的方法。 它以这种方式工作:正则表达式使用数字的字符串版本,将其数字拆分为字符串数组,最后外部ConvertAll方法从字符串数组创建一个int数组。

  int num = 1234567890; int [] arrDigits = Array.ConvertAll( System.Text.RegularExpressions.Regex.Split(num.ToString(), @"(?!^)(?!$)"), str => int.Parse(str) ); // resulting array is [1,2,3,4,5,6,7,8,9,0] 

效率明智吗?……我不确定与我在这里看到的其他一些快速答案相比。 有人必须测试它。

也许一个小循环展开?

 int num = 147483647; int nDigits = 1 + Convert.ToInt32(Math.Floor(Math.Log10(num))); byte[] array = new byte[10] { (byte)(num / 1000000000 % 10), (byte)(num / 100000000 % 10), (byte)(num / 10000000 % 10), (byte)(num / 1000000 % 10), (byte)(num / 100000 % 10), (byte)(num / 10000 % 10), (byte)(num / 1000 % 10), (byte)(num / 100 % 10), (byte)(num / 10 % 10), (byte)(num % 10)}; byte[] digits;// = new byte[nDigits]; digits = array.Skip(array.Length-nDigits).ToArray(); 

感谢上面的Log10东西..;)

有一些关于基准测试的讨论……

我已经完全展开了循环,并与Jons接受的memoized变体进行了比较,我得到了一个更快的时间: –

  static int[] ConvertToArrayOfDigits_unrolled(int num) { if (num < 10) { return new int[1] { (num % 10) }; } else if (num < 100) { return new int[2] { (num / 10 % 10), (num % 10) }; } else if (num < 1000) { return new int[3] { (num / 100 % 10), (num / 10 % 10), (num % 10)}; } else if (num < 10000) { return new int[4] { (num / 1000 % 10), (num / 100 % 10), (num / 10 % 10), (num % 10)}; } else if (num < 100000) { return new int[5] { (num / 10000 % 10), (num / 1000 % 10), (num / 100 % 10), (num / 10 % 10), (num % 10)}; } else if (num < 1000000) { return new int[6] { (num / 100000 % 10), (num / 10000 % 10), (num / 1000 % 10), (num / 100 % 10), (num / 10 % 10), (num % 10)}; } else if (num < 10000000) { return new int[7] { (num / 1000000 % 10), (num / 100000 % 10), (num / 10000 % 10), (num / 1000 % 10), (num / 100 % 10), (num / 10 % 10), (num % 10)}; } else if (num < 100000000) { return new int[8] { (num / 10000000 % 10), (num / 1000000 % 10), (num / 100000 % 10), (num / 10000 % 10), (num / 1000 % 10), (num / 100 % 10), (num / 10 % 10), (num % 10)}; } else if (num < 1000000000) { return new int[9] { (num / 100000000 % 10), (num / 10000000 % 10), (num / 1000000 % 10), (num / 100000 % 10), (num / 10000 % 10), (num / 1000 % 10), (num / 100 % 10), (num / 10 % 10), (num % 10)}; } else { return new int[10] { (num / 1000000000 % 10), (num / 100000000 % 10), (num / 10000000 % 10), (num / 1000000 % 10), (num / 100000 % 10), (num / 10000 % 10), (num / 1000 % 10), (num / 100 % 10), (num / 10 % 10), (num % 10)}; } } 

可能是我搞砸了某个地方 - 我没有太多时间玩游戏和玩游戏,但我的速度提高了20%。

如果你可以使用前导零,那就容易多了。

  void Test() { // Note: 10 is the maximum number of digits. int[] xs = new int[10]; System.Random r = new System.Random(); for (int i=0; i < 10000000; ++i) Convert(xs, r.Next(int.MaxValue)); } // Notice, I don't allocate and return an array each time. public void Convert(int[] digits, int val) { for (int i = 0; i < 10; ++i) { digits[10 - i - 1] = val % 10; val /= 10; } } 

编辑:这是一个更快的版本。 在我的计算机上,它的测试速度比Jon Skeet的两个算法要快,除了他的memoized版本:

 static void Convert(int[] digits, int val) { digits[9] = val % 10; val /= 10; digits[8] = val % 10; val /= 10; digits[7] = val % 10; val /= 10; digits[6] = val % 10; val /= 10; digits[5] = val % 10; val /= 10; digits[4] = val % 10; val /= 10; digits[3] = val % 10; val /= 10; digits[2] = val % 10; val /= 10; digits[1] = val % 10; val /= 10; digits[0] = val % 10; val /= 10; } 

除法和mod往往是缓慢的操作。 我想知道使用乘法和减法的解决方案是否会更快并且似乎(在我的计算机上):

  public static void ConvertToArrayOfDigits2(int value, int[] digits) { double v = value; double vby10 = v * .1; for (int index = digits.Length - 1; index >= 0; index--) { int ivby10 = (int)vby10; digits[index] = (int)(v)- ivby10* 10; v = ivby10; vby10 = ivby10 * .1; } } 

我传入一个数组,而不是每次都分配它来取出内存分配器和长度。 如果数组长于数字,则此版本将生成前导零。 与Jon的示例的类似转换版本相比:

  public static void ConvertToArrayOfDigits(int value, int[] digits){ for (int index = digits.Length - 1; index >= 0; index--) { digits[index] = value % 10; value = value / 10; } } 

no divide / mod版本花费了大约50倍的时间来生成给定数量的所有数组。 我也试过使用浮动,它只慢了约5-10%(双版本比浮动版本更快)。

只是因为它困扰我,这是一个展开的版本,再次稍微快一点:

  public static void ConvertToArrayOfDigits3(int value, int[] digits) { double v = value; double vby10 = v * .1; int ivby10; switch(digits.Length -1){ default: throw new ArgumentOutOfRangeException(); case 10: ivby10 = (int)vby10; digits[10] = (int)(v) - ivby10 * 10; v = ivby10; vby10 = ivby10 * .1; goto case 9; case 9: ivby10 = (int)vby10; digits[9] = (int)(v) - ivby10 * 10; v = ivby10; vby10 = ivby10 * .1; goto case 8; case 8: ivby10 = (int)vby10; digits[8] = (int)(v) - ivby10 * 10; v = ivby10; vby10 = ivby10 * .1; goto case 7; case 7: ivby10 = (int)vby10; digits[7] = (int)(v) - ivby10 * 10; v = ivby10; vby10 = ivby10 * .1; goto case 6; case 6: ivby10 = (int)vby10; digits[6] = (int)(v) - ivby10 * 10; v = ivby10; vby10 = ivby10 * .1; goto case 5; case 5: ivby10 = (int)vby10; digits[5] = (int)(v) - ivby10 * 10; v = ivby10; vby10 = ivby10 * .1; goto case 4; case 4: ivby10 = (int)vby10; digits[4] = (int)(v) - ivby10 * 10; v = ivby10; vby10 = ivby10 * .1; goto case 3; case 3: ivby10 = (int)vby10; digits[3] = (int)(v) - ivby10 * 10; v = ivby10; vby10 = ivby10 * .1; goto case 2; case 2: ivby10 = (int)vby10; digits[2] = (int)(v) - ivby10 * 10; v = ivby10; vby10 = ivby10 * .1; goto case 1; case 1: ivby10 = (int)vby10; digits[1] = (int)(v) - ivby10 * 10; v = ivby10; vby10 = ivby10 * .1; goto case 0; case 0: ivby10 = (int)vby10; digits[0] = (int)(v) - ivby10 * 10; break; } } 

根据我的测试,每次分配一个新的int []占用了大量的时间。 如果您知道这些值将在下次调用之前使用一次并丢弃,则可以重用静态数组以显着提高速度:

  private static readonly int[] _buffer = new int[10]; public static int[] ConvertToArrayOfDigits(int value) { for (int index = 9; index >= 0; index--) { _buffer[index] = value % 10; value = value / 10; } return _buffer; } 

为了保持代码较小,我为较小的数字返回尾随零,但这可以通过使用9个不同的静态数组(或数组数组)轻松更改。

或者,可以提供2个单独的ConvertToArrayOfDigits方法,一个采用预先创建的int数组作为额外参数,另一个采用不会创建结果缓冲区的方法,然后再调用第一个方法。

  public static void ConvertToArrayOfDigits(int value, int[] digits) { ... } public static int[] ConvertToArrayOfDigits(int value) { int size = DetermineDigitCount(value); int[] digits = new int[size]; ConvertToArrayOfDigits(value, digits); return digits; } 

这样,如果调用者的用例允许,可能会由调用者创建一个静态可重用缓冲区。

我没有对这个或任何东西进行基准测试,但我认为这将是最简单的答案。 如我错了请纠正我。

  Dim num As Integer = 147483647 Dim nDigits As Integer = 1 + Convert.ToInt32(Math.Floor(Math.Log10(num))) Dim result(nDigits - 1) As Integer For a As Integer = 1 To nDigits result(a - 1) = Int(num / (10 ^ (nDigits - a))) Mod 10 Next 

**编辑**

修改了这个function,因为指数似乎非常昂贵。

 Private Function Calc(ByVal num As Integer) As Integer() Dim nDigits As Int64 = 1 + Convert.ToInt64(Math.Floor(Math.Log10(num))) Dim result(nDigits - 1) As Integer Dim place As Integer = 1 For a As Integer = 1 To nDigits result(nDigits - a) = Int(num / place) Mod 10 place = place * 10 Next Return result End Function 

该基准测试值约为775k / sec(数字为9位或更少)。 将最大数字降至7,其长度为885k / s。 5个数字,1.1米/秒。