我希望将一个整数的各个数字分离出来,例如 12345,变成一个字节数组 {1,2,3,4,5},但是我需要最高效的方式来实现这一点,因为我的程序需要执行数百万次。
有什么建议吗?谢谢。
我希望将一个整数的各个数字分离出来,例如 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;
}
请注意,这不支持负数......您需要支持吗?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;
}
注意,目前这个程序并不尝试保证线程安全。您可能需要添加内存屏障(memory barrier),以确保对缓存结果的写入不会在单个结果中的写入可见之前就已经可见了。除非我绝对必须这样做,否则我已经放弃了理解这些事情的尝试。我相信您可以通过努力使它无锁化,但如果您真的需要,您应该请一个非常聪明的人来做。
编辑:我刚意识到“大数”情况可以利用“小数”情况——将大数分成两个小数,并使用缓存的结果。我晚饭后会试一下并编写基准测试...
编辑:好吧,你准备好写一堆代码了吗?我意识到,至少对于均匀随机数,你会更频繁地得到“大”数字而不是小数字——所以你需要针对这一点进行优化。当然,对于真实数据可能不是这样,但无论如何...这意味着我现在按照相反的顺序进行大小测试,希望首先得到大数。
我已经为原始代码、简单的记忆化代码和极度展开的代码编写了一个基准测试。
结果(毫秒):
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<int, int[]> 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
}
for (int i = 0; i < size; i++)
进行优化,使其比 for (int i = size - 1; i >= 0; i--)
更快。有点傻,我知道 :( 我主要是为了可读性。 - Thorarin1 + 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<byte> digits = new List<byte>();
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();
num != 0
,则可以更改以处理负数。'Will' 和 'Does'?我非常喜欢在编写代码、分析和确定瓶颈之后优化代码。
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的已被接受的记忆化变体进行了比较,我用这个方法始终获得更快的时间:
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%。
仅供娱乐,这里有一种使用单个C#语句分离所有数字的方法。它的工作原理是:正则表达式使用数字的字符串版本,将其数字拆分为字符串数组,最后外部的ConvertAll方法从字符串数组创建int数组。
int num = 1234567890;
int [] arrDigits = Array.ConvertAll<string, int>(
System.Text.RegularExpressions.Regex.Split(num.ToString(), @"(?!^)(?!$)"),
str => int.Parse(str)
);
// resulting array is [1,2,3,4,5,6,7,8,9,0]
就效率而言,相比于其他快速答案,我不确定。需要有人进行测试。
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的两个算法都要快,除了他的记忆化版本:
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;
}
除法和取模运算往往是较慢的操作。我想找出是否使用乘法和减法的解决方案会更快,而且似乎确实如此(在我的电脑上):
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;
}
}
我传递一个数组而不是每次分配它,以消除内存分配器和长度的影响。如果数组比数字更长,这个版本将产生前导零。与乔恩示例的类似转换版本相比:
public static void ConvertToArrayOfDigits(int value, int[] digits){
for (int index = digits.Length - 1; index >= 0; index--) {
digits[index] = value % 10;
value = value / 10;
}
}
没有除法/模数版本需要大约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;
}
}
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;
}
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;
}
这样,如果使用情况允许,调用者可以自行创建静态可重用缓冲区。