Come si può ottenere la prima cifra in un int(C#)?




tipo dato double c# (20)

In C #, qual è il modo migliore per ottenere la prima cifra in un int? Il metodo che ho trovato è quello di trasformare l'int in una stringa, trovare il 1 ° carattere della stringa, quindi tornare a un int.

int start = Convert.ToInt32(curr.ToString().Substring(0, 1));

Mentre questo fa il lavoro, sembra che ci sia probabilmente una buona, semplice, soluzione basata sulla matematica per un simile problema. La manipolazione delle stringhe sembra goffo.

Modifica: indipendentemente dalle differenze di velocità, mystring [0] invece di Substring () è ancora solo manipolazione di stringhe


Se pensi che la risposta di Keltex sia brutta, prova questo, è DAVVERO brutto, e anche più veloce. Effettua la ricerca binaria srotolata per determinare la lunghezza.

 ... leading code along the same lines
/* i<10000 */
if (i >= 100){
  if (i >= 1000){
    return i/1000;
  }
  else /* i<1000 */{
    return i/100;
  }
}
else /* i<100*/ {
  if (i >= 10){
    return i/10;
  }
  else /* i<10 */{
    return i;
  }
}

PS MartinStettner ha avuto la stessa idea.


So che non è C #, ma è sorprendente la curiosità che in Python il "ottenere il primo carattere della rappresentazione della stringa del numero" sia il più veloce!

EDIT : no, ho fatto un errore, ho dimenticato di ricostruire l'int, mi dispiace. La versione srotolata è la più veloce.

$ cat first_digit.py
def loop(n):
    while n >= 10:
        n /= 10
    return n

def unrolled(n):
    while n >= 100000000: # yea... unlimited size int supported :)
        n /= 100000000
    if n >= 10000:
        n /= 10000
    if n >= 100:
        n /= 100
    if n >= 10:
        n /= 10
    return n

def string(n):
    return int(str(n)[0])
$ python -mtimeit -s 'from first_digit import loop as test' \
    'for n in xrange(0, 100000000, 1000): test(n)'
10 loops, best of 3: 275 msec per loop
$ python -mtimeit -s 'from first_digit import unrolled as test' \
    'for n in xrange(0, 100000000, 1000): test(n)'
10 loops, best of 3: 149 msec per loop
$ python -mtimeit -s 'from first_digit import string as test' \
    'for n in xrange(0, 100000000, 1000): test(n)'
10 loops, best of 3: 284 msec per loop
$

Aveva la stessa idea di Lennaert

int start = number == 0 ? 0 : number / (int) Math.Pow(10,Math.Floor(Math.Log10(Math.Abs(number))));

Funziona anche con numeri negativi.


Ha fatto alcuni test con uno dei miei colleghi qui, e ho scoperto che la maggior parte delle soluzioni non funziona per i numeri sotto 0.

  public int GetFirstDigit(int number)
    {
        number = Math.Abs(number); <- makes sure you really get the digit!

        if (number < 10)
        {
            return number;
        }
        return GetFirstDigit((number - (number % 10)) / 10);
    }

int i = 4567789;
int digit1 = int.Parse(i.ToString()[0].ToString());

Solo per darti un'alternativa, potresti dividere ripetutamente il numero intero per 10, e poi ripristinare un valore una volta raggiunto lo zero. Poiché le operazioni con le stringhe sono generalmente lente, questo potrebbe essere più veloce della manipolazione delle stringhe, ma non è affatto elegante.

Qualcosa come questo:

while(curr>=10)
     curr /= 10;

int temp = i;
while (temp >= 10)
{
    temp /= 10;
}

Risultato in temp


Prova questo

public int GetFirstDigit(int number) {
  if ( number < 10 ) {
    return number;
  }
  return GetFirstDigit ( (number - (number % 10)) / 10);
}

MODIFICARE

Diverse persone hanno richiesto la versione loop

public static int GetFirstDigitLoop(int number)
{
    while (number >= 10)
    {
        number = (number - (number % 10)) / 10;
    }
    return number;
}

while (i > 10)
{
   i = (Int32)Math.Floor((Decimal)i / 10);
}
// i is now the first int

Ecco un modo più semplice che non coinvolge il loop

int number = 1234
int firstDigit = Math.Floor(number/(Math.Pow(10, number.ToString().length - 1))

Questo ci darebbe 1234 / Math.Pow (10, 4 - 1) = 1234/1000 = 1


benchmark

In primo luogo, è necessario decidere cosa intendi per "migliore" soluzione, ovviamente tenendo conto dell'efficienza dell'algoritmo, della sua leggibilità / manutenibilità e della probabilità che si verifichino errori in futuro. Tuttavia, test di unità accurati possono generalmente evitare questi problemi.

Ho eseguito ciascuno di questi esempi 10 milioni di volte, e il valore dei risultati è il numero di ElapsedTicks che sono passati.

Senza ulteriori indugi, dal più lento al più veloce, gli algoritmi sono:

Convertire in una stringa, prendi il primo carattere

int firstDigit = (int)(Value.ToString()[0]) - 48;

risultati:

12,552,893 ticks

Usando un logaritmo

int firstDigit = (int)(Value / Math.Pow(10, (int)Math.Floor(Math.Log10(Value))));

risultati:

9,165,089 ticks

looping

while (number >= 10)
    number /= 10;

risultati:

6,001,570 ticks

Condizionali

int firstdigit;
if (Value < 10)
     firstdigit = Value;
else if (Value < 100)
     firstdigit = Value / 10;
else if (Value < 1000)
     firstdigit = Value / 100;
else if (Value < 10000)
     firstdigit = Value / 1000;
else if (Value < 100000)
     firstdigit = Value / 10000;
else if (Value < 1000000)
     firstdigit = Value / 100000;
else if (Value < 10000000)
     firstdigit = Value / 1000000;
else if (Value < 100000000)
     firstdigit = Value / 10000000;
else if (Value < 1000000000)
     firstdigit = Value / 100000000;
else
     firstdigit = Value / 1000000000;

risultati:

1,421,659 ticks

Ciclo srotolato e ottimizzato

if (i >= 100000000) i /= 100000000;
if (i >= 10000) i /= 10000;
if (i >= 100) i /= 100;
if (i >= 10) i /= 10;

risultati:

1,399,788 ticks

Nota:

ogni test chiama Random.Next() per ottenere il prossimo int


int myNumber = 8383;
char firstDigit = myNumber.ToString()[0];
// char = '8'

Molto semplice (e probabilmente abbastanza veloce perché comporta solo confronti e una divisione):

if(i<10)
   firstdigit = i;
else if (i<100)
   firstdigit = i/10;
else if (i<1000)
   firstdigit = i/100;
else if (i<10000)
   firstdigit = i/1000;
else if (i<100000)
   firstdigit = i/10000;
else (etc... all the way up to 1000000000)

start = getFirstDigit(start);   
public int getFirstDigit(final int start){
    int number = Math.abs(start);
    while(number > 10){
        number /= 10;
    }
    return number;
}

o

public int getFirstDigit(final int start){
  return getFirstDigit(Math.abs(start), true);
}
private int getFirstDigit(final int start, final boolean recurse){
  if(start < 10){
    return start;
  }
  return getFirstDigit(start / 10, recurse);
}

Formula non iterativa:

public static int GetHighestDigit(int num)
{
    if (num <= 0)
       return 0; 

    return (int)((double)num / Math.Pow(10f, Math.Floor(Math.Log10(num))));
}

Il meglio che posso inventare è:

int numberOfDigits = Convert.ToInt32(Math.Floor( Math.Log10( value ) ) );

int firstDigit = value / Math.Pow( 10, numberOfDigits );

...

Non molto carina :)

[Modificato: la prima risposta è stata davvero pessima :)]

[Modifica 2: probabilmente consiglierei le soluzioni che manipolano le stringhe, sebbene]

[Modifica 3: la formattazione del codice è bella :)]


int start = curr;
while (start >= 10)
  start /= 10;

Questo è più efficiente di un approccio ToString () che internamente deve implementare un ciclo simile e deve costruire (e analizzare) un oggetto stringa sulla strada ...


Usando tutti gli esempi qui sotto per ottenere questo codice:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Diagnostics;

namespace Benfords
{
    class Program
    {
        static int FirstDigit1(int value)
        {
            return Convert.ToInt32(value.ToString().Substring(0, 1));
        }

        static int FirstDigit2(int value)
        {
            while (value >= 10) value /= 10;
            return value;
        }


        static int FirstDigit3(int value)
        {
            return (int)(value.ToString()[0]) - 48;
        }

        static int FirstDigit4(int value)
        {
            return (int)(value / Math.Pow(10, (int)Math.Floor(Math.Log10(value))));
        }

        static int FirstDigit5(int value)
        {
            if (value < 10) return value;
            if (value < 100) return value / 10;
            if (value < 1000) return value / 100;
            if (value < 10000) return value / 1000;
            if (value < 100000) return value / 10000;
            if (value < 1000000) return value / 100000;
            if (value < 10000000) return value / 1000000;
            if (value < 100000000) return value / 10000000;
            if (value < 1000000000) return value / 100000000;
            return value / 1000000000;
        }

        static int FirstDigit6(int value)
        {
            if (value >= 100000000) value /= 100000000;
            if (value >= 10000) value /= 10000;
            if (value >= 100) value /= 100;
            if (value >= 10) value /= 10;
            return value;
        }

        const int mcTests = 1000000;

        static void Main(string[] args)
        {
            Stopwatch lswWatch = new Stopwatch();
            Random lrRandom = new Random();

            int liCounter;

            lswWatch.Start();
            for (liCounter = 0; liCounter < mcTests; liCounter++)
                FirstDigit1(lrRandom.Next());
            lswWatch.Stop();
            Console.WriteLine("Test {0} = {1} ticks", 1, lswWatch.ElapsedTicks);

            lswWatch.Reset();
            lswWatch.Start();
            for (liCounter = 0; liCounter < mcTests; liCounter++)
                FirstDigit2(lrRandom.Next());
            lswWatch.Stop();
            Console.WriteLine("Test {0} = {1} ticks", 2, lswWatch.ElapsedTicks);

            lswWatch.Reset();
            lswWatch.Start();
            for (liCounter = 0; liCounter < mcTests; liCounter++)
                FirstDigit3(lrRandom.Next());
            lswWatch.Stop();
            Console.WriteLine("Test {0} = {1} ticks", 3, lswWatch.ElapsedTicks);

            lswWatch.Reset();
            lswWatch.Start();
            for (liCounter = 0; liCounter < mcTests; liCounter++)
                FirstDigit4(lrRandom.Next());
            lswWatch.Stop();
            Console.WriteLine("Test {0} = {1} ticks", 4, lswWatch.ElapsedTicks);

            lswWatch.Reset();
            lswWatch.Start();
            for (liCounter = 0; liCounter < mcTests; liCounter++)
                FirstDigit5(lrRandom.Next());
            lswWatch.Stop();
            Console.WriteLine("Test {0} = {1} ticks", 5, lswWatch.ElapsedTicks);

            lswWatch.Reset();
            lswWatch.Start();
            for (liCounter = 0; liCounter < mcTests; liCounter++)
                FirstDigit6(lrRandom.Next());
            lswWatch.Stop();
            Console.WriteLine("Test {0} = {1} ticks", 6, lswWatch.ElapsedTicks);

            Console.ReadLine();
        }
    }
}

Ottengo questi risultati su un AMD Ahtlon 64 X2 Dual Core 4200+ (2,2 GHz):

Test 1 = 2352048 ticks
Test 2 = 614550 ticks
Test 3 = 1354784 ticks
Test 4 = 844519 ticks
Test 5 = 150021 ticks
Test 6 = 192303 ticks

Ma ottieni questi su un AMD FX 8350 Eight Core (4,00 GHz)

Test 1 = 3917354 ticks
Test 2 = 811727 ticks
Test 3 = 2187388 ticks
Test 4 = 1790292 ticks
Test 5 = 241150 ticks
Test 6 = 227738 ticks

Quindi, se il metodo 5 o 6 è più veloce dipende dalla CPU, posso solo supporre che questo sia dovuto al fatto che la previsione del ramo nel processore di comando della CPU è più intelligente sul nuovo processore, ma non ne sono sicuro.

Non ho CPU Intel, forse qualcuno potrebbe testarlo per noi?


Un approccio matematico ovvio, ma lento, è:

int firstDigit = (int)(i / Math.Pow(10, (int)Math.Log10(i))));

variazione sulla risposta di Anton:

 // cut down the number of divisions (assuming i is positive & 32 bits)
if (i >= 100000000) i /= 100000000;
if (i >= 10000) i /= 10000;
if (i >= 100) i /= 100;
if (i >= 10) i /= 10;




c#