Як ви можете отримати першу цифру в int (C #)?


82

У C #, який найкращий спосіб отримати 1-у цифру в int? Метод, який я придумав, - перетворити int на рядок, знайти 1-й символ рядка, а потім повернути його назад на int.

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

Незважаючи на те, що це робить роботу, здається, що існує, мабуть, гарне, просте математичне рішення такої проблеми. Струнна маніпуляція відчуває себе незграбно.

Редагувати: незалежно від різниці швидкості, mystring [0] замість Substring () все ще є просто маніпуляцією рядками


Це навіть повільніше, ніж рекурсивний метод. =)
Дж. Стін

мій поганий, я забув скинути секундомір =), це дійсно повільніше за інших
Quan Mai

2
Це не вдасться для від’ємних чисел.
плінтус

2
перша цифра int завжди дорівнює 1 з нульовим придушенням (01010101010100101011011011100101) LOL
Zachary Scott

Відповіді:



222

Тести

По-перше, ви повинні визначитися з тим, що ви маєте на увазі під "найкращим" рішенням, звичайно, що враховує ефективність алгоритму, його читабельність / ремонтопридатність та ймовірність появи помилок у майбутньому. Однак ретельні модульні тести можуть уникнути цих проблем.

Я провів кожен із цих прикладів 10 мільйонів разів, і значення результатів - це кількість ElapsedTicksпройдених.

Без зайвих сумнівів, від найповільнішого до найшвидшого, алгоритми:

Перетворюючи на рядок, приймаємо перший символ

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

Результати:

12,552,893 ticks

Використання логарифму

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

Результати:

9,165,089 ticks

Зациклення

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

Результати:

6,001,570 ticks

Умовні

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;

Результати:

1,421,659 ticks

Розгорнутий та оптимізований цикл

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

Результати:

1,399,788 ticks

Примітка:

кожен тестовий дзвінок Random.Next()отримує наступнийint


Оцінив їх на якому наборі цілих чисел?
mqp

18
Це оооооочень тривіально. Я ціную те, що ви зробили, і це досить інформативно, але це настільки надмірно для 99% програм. Я боюся, що деякі програмісти-початківці можуть скласти погане враження. (тверда, передчасна оптимізація щодо читабельності) Однак це все одно цікаво. :)
Саманта Бранхам

2
+1 для контрольних показників, але я погоджуюсь, що це загальний надмір. Якщо вас ЦЕ турбує продуктивність, то вам, мабуть, не слід використовувати C # в першу чергу :)
GrahamS

15
+1 - Чудові речі. Це той тип позолоченої надмірної моделі, що просто дає зрозуміти, що ви любите кодування. Багато вам похвали. І ні, Стюарт, я не думаю, що це забруднить розум нічого не підозрюючого новачка. Сподіваємось, це навчить їх розвивати любов до програмного забезпечення.
Марк Бріттінгем

2
@Vikash - приведення а charдо оцінювача intдо кодової точки символу, тому приведення символу 0до оцінки до 48. Віднімання 48 ефективно робить перетворення в ціле число. Як ще один приклад, приведення персонажа 5до intоцінки оцінюється до 53, а віднімання 48 від цього призводить до 5.
Джон Раш

31

Спробуйте це

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

РЕДАГУВАТИ

Кілька людей просили циклічну версію

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

1
Рекурсія на щось подібне ... Ганьба. Вкладіть туди петлю.
Welbog

@Welbog :), Зазвичай я б. Але у мене є багато ностальгії навколо цього конкретного питання. Це майже перші запитання, які мені коли-небудь задавали для призначення CS. У той час я писав по суті це рішення.
JaredPar

Це той випадок, коли "nice" = повільно
Keltex

Я сподіваюся, оптимізатор побачить першу версію як щось, що можна оптимізувати за допомогою зворотного виклику, і в цьому випадку це не буде нічим повільнішим.
rmeador

1
Але, @Jared, віднімання останньої цифри тут абсолютно непотрібне. Просто розділіть на 10.
Конрад Рудольф

26

Найкраще, що я можу придумати, це:

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

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

Ви можете уникнути непотрібних перетворень між цілими і подвійними, використовуючи divisor = Convert.ToInt32 (Math.Pow (10, Math.Floor (Math.Log10 (value))))); firstDigit = значення / дільник;
MartinStettner

На жаль, це не працює, коли значення 1 (повертає 0) або якщо значення від’ємне. Виправлено int numberOfDigits = 1 + Convert.ToInt32 (Math.Floor (Math.Log10 (Math.Abs ​​(value)))));
ДанДан

18

варіація відповіді Антона:

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

Я думаю, що я б просто залишив це на /10і /1000. Ви все одно отримуєте перевагу ділитись більш агресивно, але з дещо меншим безладом. +1 для творчості :)
Саманта Бранхам

Мені нагадує функцію в bcl, яку хтось виявив, метод був досить потворний для читання, але він, в основному, робив циклічні порівняння і групував порівняння в групи по десять або близько того, знаючи, що сучасний процесор зможе краще обробляти групу порівнює за раз.
meandmycode

Це лише фантастично потворно, тому що C # не має можливості генерувати ці consts під час компіляції (а робити це під час виконання буде дорожче, ніж те, що ви намагаєтеся запобігти). У такій мові, як Lisp, де ви контролюєте час eval, швидкий та елегантний способи однакові. :-)
Кен

Розгортання циклу, безумовно, краще, ніж глупство операторів if / else. Це і елегантно, і швидкодіюче.
Рандольфо

3
якщо ви зробите перший "if" a "while", він також працює з 64-бітними (але ще "потворніший"). +1
MartinStettner

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

Хіба це не ідентично тому, що я розмістив? Він просто використовує [] замість Substring
Діна

Ні: він повертає символ, а не рядок, і пошук [] повинен бути швидшим
Джоел Коухорн,

Це правда, але це все той же основний метод - проаналізуйте подання рядка. Я думаю, що @Dinah шукає щось ... ти знаєш .... інше.
Рандольфо

@Randolpho: точно. Вибачте, якщо мені це було незрозуміло. Я оновив питання для уточнення.
Діна

Я також повернув би firstDigit-'0 ', щоб отримати int. (це працює в c ... я думаю, це в c #)
Ніколас Ірісаррі

5

Мав таку ж ідею, як Леннаерт

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

Це також працює з від’ємними числами.


4

Якщо ви вважаєте відповідь Келтекса потворною, спробуйте цю, вона НАЙДУМІ потворна, та ще швидша. Він виконує розгорнутий двійковий пошук для визначення довжини.

 ... 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 Мартін Штеттнер мав ту ж ідею.


@Rasch: Ах, захоплення! Його слід приховувати, виносити лише для особливих випадків і читати приглушеними тонами ... Хто каже, що програмування не має душі?
Mike Dunlavey

як щодоi=(...moreugly...)i>=100?i>=1000?i/1000:i/100:i>=10?i/10:i;
Девід Мердок

3

Очевидним, але повільним математичним підходом є:

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

Час, це не повинно бути зовсім повільним. Це мала бути моя пропозиція, а враховуючи, що це чисто математичне рішення без петель, можливо, це швидше, ніж багато інших рішень тут. Майте на увазі, що кожен проданий сьогодні процесор x86 має дуже, дуже швидкий і здатний процесор із плаваючою комою.
Адам Девіс,

Я думаю, вам справді слід використовувати Math.Log10 тут
MartinStettner

Дякую, Мартіне - я мало використовую математичні бібліотеки .NET і забув, що це Log і Log10 замість Log і Ln.
mqp

Ну, Адаме, як мінімум для цього потрібно взяти 10 ^ (N-1) (n - кількість цифр), плюс потрібно взяти логарифм. Порівняно з циклічною відповіддю (де ви ділите N - 1 раз), мені здається, що вона повинна бути принаймні трохи повільнішою. Але мене регулярно дивують тести, тому хто знає.
mqp

1
Крім того, ОП вимагало більш значущого висловлення, не зважаючи на результати, і це найбільш прямий (математичний) спосіб висловити те, що шукається.
гарпо

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

В результаті temp


@ck: Яке округлення видано? У цьому випадку ділення є цілочисельним, кожне ділення точно відсікає останню цифру ...
MartinStettner

3

Я знаю, що це не C #, але дивно, що цікаво, що в python "отримати перший символ рядкового подання числа" швидше!

EDIT : ні, я помилився, забув побудувати ще раз int, вибачте. Розгорнута версія - найшвидша.

$ 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
$

Але чи перетворює його назад у ціле число?
Самуель

вау, зараз це має сенс -.-
ZeD

ну, він міг би сказати, що це IronPython, щоб зробити це трохи менш тематичним
Раві

3

Я просто наткнувся на це старе питання і відчув схильність запропонувати ще одну пропозицію, оскільки жодна з інших відповідей поки що не повертає правильного результату для всіх можливих вхідних значень, і це все одно можна зробити швидше:

public static int GetFirstDigit( int i )
{
    if( i < 0 && ( i = -i ) < 0 ) return 2;
    return ( i < 100 ) ? ( i < 1 ) ? 0 : ( i < 10 )
            ? i : i / 10 : ( i < 1000000 ) ? ( i < 10000 )
            ? ( i < 1000 ) ? i / 100 : i / 1000 : ( i < 100000 )
            ? i / 10000 : i / 100000 : ( i < 100000000 )
            ? ( i < 10000000 ) ? i / 1000000 : i / 10000000
            : ( i < 1000000000 ) ? i / 100000000 : i / 1000000000;
}

Це працює для всіх підписаних цілих значень включно, -2147483648яке є найменшим підписаним цілим числом і не має позитивного відповідника. Math.Abs( -2147483648 )запускає a System.OverflowExceptionі - -2147483648обчислює-2147483648 .

Реалізація може розглядатися як поєднання переваг двох найшвидших реалізацій на даний момент. Він використовує двійковий пошук і уникає зайвих поділів. Швидкий орієнтир з індексом циклу із 100 000 000 ітерацій показує, що це вдвічі швидше, ніж найшвидша на даний момент реалізація.

Він закінчується після 2 829 581 кліщів.

Для порівняння я також виміряв виправлений варіант найшвидшого впровадження, який взяв 5664627 кліщів.

public static int GetFirstDigitX( int i )
{
    if( i < 0 && ( i = -i ) < 0 ) return 2;
    if( i >= 100000000 ) i /= 100000000;
    if( i >= 10000 ) i /= 10000;
    if( i >= 100 ) i /= 100;
    if( i >= 10 ) i /= 10;
    return i;
}

Для прийнятої відповіді з такою ж корекцією для цього тесту на моєму комп’ютері потрібно було 16 561 929 галочок.

public static int GetFirstDigitY( int i )
{
    if( i < 0 && ( i = -i ) < 0 ) return 2;
    while( i >= 10 )
        i /= 10;
    return i;
}

Такі прості функції легко перевірити на правильність, оскільки ітерація всіх можливих цілочисельних значень займає на поточному обладнанні не набагато більше секунд. Це означає, що менш важливо впроваджувати їх у надзвичайно зручний для читання спосіб, оскільки згодом просто не буде потреби виправити помилку всередині них.


2

Дуже просто (і, мабуть, досить швидко, оскільки воно включає лише порівняння та один поділ):

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)

1
А потім 10k, 100k, 1mil, 10mil, 100mil тощо?
Павел Краковяк,

Я б зробив це так. Набагато ефективніше, ніж ця рекурсивна функція.
Keltex

Ця відповідь найкраща з групи.
Брайан

Ефективність, придбана за рахунок дійсно справді потворного, повторюваного, неможливого коду. Що якби ви випадково пропустили звання? Ой.
Рандольфо

Ви дійсно повинні змінити порядок цих умов так, щоб найімовірніші були на вершині ...
Джейсон Пунйон

1

Провів деякі тести з одним із моїх колег тут і виявив, що більшість рішень не працюють для цифр менше 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);
    }

1

Використовуючи всі приклади нижче, щоб отримати цей код:

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

Я отримую такі результати на AMD Ahtlon 64 X2 Dual Core 4200+ (2,2 ГГц):

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

Але отримайте їх на AMD FX 8350 Eight Core (4,00 ГГц)

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

Тож чи швидший метод 5 чи 6 залежить від центрального процесора, я можу лише припустити, що це тому, що передбачення гілок у командному процесорі центрального процесора є розумнішим на новому процесорі, але я насправді не впевнений.

У мене немає процесорів Intel, можливо, хтось може перевірити це для нас?


1

Перевірте і цей:

int get1digit(Int64 myVal)
{
    string q12 = myVal.ToString()[0].ToString();
    int i = int.Parse(q12);
    return i;
}

Також добре, якщо ви хочете кілька чисел:

int get3digit(Int64 myVal) //Int64 or whatever numerical data you have
{
    char mg1 = myVal.ToString()[0];
    char mg2 = myVal.ToString()[1];
    char mg3 = myVal.ToString()[2];
    char[] chars = { mg1, mg2, mg3 };
    string q12= new string(chars);
    int i = int.Parse(q12);
    return i;
}

Чи можете ви додати якесь пояснення до своєї відповіді? Це питання вже має 24 інші відповіді - було б добре знати, чому ми повинні використовувати вашу, а не інші.
Wai Ha Lee,

З оригінальним рішенням я отримав деякі помилки, використовуючи велику кількість. Моє рішення чистіше. Ласкаво просимо.
теодорік8.

Ваше рішення не вдається -1.
Wai Ha Lee,

:) використовуйте це тоді: if (myVal.ToString () [0]! = '-') {....} і - знак не є числом!
теодорік8.

Чому б не перевірити число не від’ємне замість того, щоб перевірити рядок?
Wai Ha Lee,

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

Чому ви конвертуєте в десяткову? Також підлога не потрібна.
MartinStettner

Це було там для потенційних питань округлення, які насправді не потрібні.
cjk

Має бути в той час, як (i> = 10), інакше це не вдасться для кратних 10.
Білл Ящірка

0

Неітераційна формула:

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

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

Реалізація журналу є ітеративною, оскільки вимагає алгоритму пошуку коренів
Джаспер Беккерс

0

Щоб дати вам альтернативу, ви можете кілька разів розділити ціле число на 10, а потім повернути одне значення, як тільки досягнете нуля. Оскільки рядкові операції, як правило, повільні, це може бути швидше, ніж маніпулювання рядками, але аж ніяк не елегантно.

Щось на зразок цього:

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

Зробіть умову while (curr> 10) і вам не потрібно prevValue
MartinStettner

> = потрібно. Перша цифра 10 повинна бути 1, а не 10
Діна

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

або

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

Має працювати зараз. Також додана рекурсивна функція з пасткою для абс.
sdellysse

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

Це ефективніше, ніж підхід ToString (), який внутрішньо повинен реалізувати подібний цикл і повинен побудувати (і проаналізувати) рядовий об'єкт на шляху ...


0

Дуже простий спосіб отримати останню цифру:

int myInt = 1821;

int lastDigit = myInt - ((myInt/10)*10); // 1821 - 1820 = 1

3
Це приємно, але питання полягає в тому, щоб отримати першу цифру, а не останню.
Діна,

0

Це те, що я зазвичай роблю, будь ласка, зверніться до моєї функції нижче:

Ця функція може витягти появу першого числа з будь-якого рядка, який ви можете змінити та використовувати цю функцію відповідно до вашого використання

   public static int GetFirstNumber(this string strInsput)
    {
        int number = 0;
        string strNumber = "";
        bool bIsContNo = true;
        bool bNoOccued = false;

        try
        {
            var arry = strInsput.ToCharArray(0, strInsput.Length - 1);

            foreach (char item in arry)
            {
                if (char.IsNumber(item))
                {
                    strNumber = strNumber + item.ToString();

                    bIsContNo = true;

                    bNoOccued = true;
                }
                else
                {
                    bIsContNo = false;
                }

                if (bNoOccued && !bIsContNo)
                {
                    break;
                }


            }

            number = Convert.ToInt32(strNumber);

        }
        catch (Exception ex)
        {

            return 0;
        }

        return number;

    }

-2

Ось простіший спосіб, який не передбачає циклів

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

Це дало б нам 1234 / Math.Pow (10, 4 - 1) = 1234/1000 = 1


Неможливо неявно перетворити тип "double" у "int".
Юнес

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.