solo separar rellenar numeros numero mostrar izquierda individuales dividir digitos decimales con ceros c# .net performance arrays numbers

c# - rellenar - ¿La forma más rápida de separar los dígitos de un int en una matriz en.NET?



separar numero en digitos c# (11)

Quiero separar los dígitos de un entero, digamos 12345, en una matriz de bytes {1, 2, 3, 4, 5}, pero quiero la manera más eficaz de hacerlo, porque mi programa lo hace millones de veces. .

¿Alguna sugerencia? Gracias.


''Will'' vs ''Does''? Soy un gran admirador de la optimización del código después de que ha sido escrito, perfilado, y se ha determinado que es el cuello de botella.


1 + Math.Log10 (num) dará la cantidad de dígitos sin ninguna búsqueda / bucle:

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; }

Editar: Posiblemente más bonito:

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; }


La asignación de un nuevo int [] cada vez ocupa una cantidad significativa de tiempo según mi prueba. Si sabe que estos valores se usarán una vez y se descartarán antes de la próxima llamada, en su lugar podría reutilizar una matriz estática para una mejora de velocidad significativa:

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; }

para mantener el código pequeño, estoy devolviendo cero para números más pequeños, pero esto podría cambiarse fácilmente usando 9 matrices estáticas diferentes (o una matriz de matrices).

Alternativamente, se podrían proporcionar 2 métodos separados ConvertToArrayOfDigits, uno tomando una matriz int preestablecida como un parámetro extra, y otra sin la que crea el búfer resultante antes de llamar al primer método.

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; }

De esta forma, correspondería a quien llama crear potencialmente un búfer reutilizable estático si su uso lo permite.


Millones de veces no es mucho.

// 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();

Tenga en cuenta que esto podría modificarse para hacer frente a los números negativos si cambia el byte a sbyte y prueba num != 0 .


No he comparado esto ni nada, pero creo que esta sería la respuesta más simple. Corrígeme si estoy equivocado.

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

** EDIT **

Revisó la función porque los exponentes parecen ser muy costosos.

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

Estos puntos de referencia rondan los 775k / seg (para números de 9 dígitos o menos). Coloque los dígitos máximos en 7 y los bancos en 885k / s. 5 dígitos a 1.1 m / s.


Qué tal si:

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; }

Tenga en cuenta que esto no hará frente a los números negativos ... ¿lo necesita?

EDITAR: Aquí hay una versión que memoriza los resultados para menos de 10000, como lo sugirió Eric. Si puede absolutamente garantizar que no va a cambiar el contenido de la matriz devuelta, puede eliminar la llamada Clone . También tiene la práctica propiedad de reducir el número de cheques para determinar la longitud de los números "grandes", y los números pequeños solo pasarán por ese código una vez, de todos modos :)

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; }

Tenga en cuenta que esto no intenta ser seguro para subprocesos en este momento. Es posible que necesite agregar una barrera de memoria para asegurarse de que la escritura en los resultados memorizados no sea visible hasta que las escrituras en el resultado individual estén visibles. He renunciado a tratar de razonar sobre estas cosas a menos que sea absolutamente necesario. Estoy seguro de que puedes hacerlo sin bloqueos con esfuerzo, pero realmente deberías conseguir a alguien muy inteligente para hacerlo si realmente lo necesitas.

EDITAR: Me acabo de dar cuenta de que el caso "grande" puede hacer uso del caso "pequeño": divida el número grande en dos pequeños y use los resultados memorados. Lo probaré después de la cena y escribiré un punto de referencia ...

EDITAR: Bien, ¿listo para una gran cantidad de código? Me di cuenta de que para números uniformemente aleatorios al menos, obtendrás números "grandes" mucho más a menudo que los pequeños, por lo que debes optimizarlos para eso. Por supuesto, puede que ese no sea el caso para los datos reales, pero de todos modos ... significa que ahora hago mis pruebas de tamaño en el orden opuesto, esperando primero los números grandes.

Tengo un punto de referencia para el código original, la memoria simple y luego el código extremadamente desenrollado.

Resultados (en ms):

Simple: 3168 SimpleMemo: 3061 UnrolledMemo: 1204

Código:

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 }


Solo por diversión, aquí hay una manera de separar todos los dígitos usando solo una declaración C #. Funciona de esta manera: la expresión regular utiliza la versión de cadena del número, divide sus dígitos en una matriz de cadenas y, finalmente, el método externo ConvertAll crea una matriz int de la matriz de cadenas.

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]

Eficiencia en cuanto a ... No estoy seguro en comparación con algunas de las otras respuestas rápidas que veo aquí. Alguien debería probarlo.


convertir entero en cadena y luego usar String.Chars []


dividir y mod tienden a ser operaciones lentas. Quería saber si una solución que usa multiplicación y sustracción sería más rápida y parece ser (en mi computadora):

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; } }

Estoy pasando una matriz en lugar de asignarlo cada vez para sacar el asignador de memoria y la longitud de la ecuación. Esta versión producirá ceros a la izquierda si la matriz es más larga que el número. Comparado con una versión similarmente convertida del ejemplo de 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; } }

la versión no divide / mod tomó aproximadamente 50 veces más para generar todas las matrices hasta un número determinado. También traté de usar flotadores y fue solo un 5-10% más lento (la versión doble era más rápida que la versión flotante).

Solo porque me molestaba, aquí hay una versión desenrollada que es ligeramente más rápida de nuevo:

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; } }


Si puedes salir adelante con ceros a la izquierda es mucho más fácil.

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; } }

EDITAR: Aquí hay una versión más rápida. En mi computadora probó más rápido que dos de los algoritmos de Jon Skeet, a excepción de su versión memorada:

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; }


¿Un pequeño bucle que se desenrolla quizás?

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();

Gracias arriba por la cosa Log10 ...;)

Se ha hablado de benchmarking ...

Desplié completamente los bucles y los comparé con la variante aceptada de Jons, y recibo un tiempo consistentemente más rápido con esto:

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)}; } }

Puede ser que haya cometido un error en alguna parte: no tengo mucho tiempo para divertirme y jugar, pero lo estaba cronometrando un 20% más rápido.