Як ви можете отримати першу цифру в 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.