Как получить первую цифру в int (C #)?


82

В C #, как лучше всего получить 1-ю цифру в int? Метод, который я придумал, состоит в том, чтобы превратить int в строку, найти 1-й символ строки, а затем вернуть его в int.

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

Хотя это работает, похоже, что существует хорошее простое математическое решение такой проблемы. Манипуляции со строками кажутся неуклюжими.

Изменить: независимо от разницы в скорости mystring [0] вместо Substring () по-прежнему является просто манипуляцией со строкой


Это даже медленнее, чем рекурсивный метод. =)
— J. Steen

мой плохой, я забыл сбросить секундомер =) он действительно медленнее, чем другие
— 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 - приведение a charк intоценивается к кодовой точке символа, поэтому приведение символа 0оценивается к 48. Вычитание 48 эффективно выполняет преобразование в целое число. Другой пример: приведение персонажа 5к int
— Джон Раш,

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,

Это тот случай, когда "хороший" = медленный
— Keltex

Я надеюсь, что оптимизатор увидит в первой версии что-то, что можно оптимизировать с помощью хвостового вызова, и в этом случае он не будет медленнее.
— rmeador

1
Но, @Jared, вычитание последней цифры здесь совершенно не нужно. Просто разделите на 10.
— Конрад Рудольф

26

Лучшее, что я могу придумать, это:

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

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

Вы можете избежать ненужных преобразований между целыми числами и числами типа double, используя 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))));
— DanDan

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 # нет способа сгенерировать эти константы во время компиляции (а выполнение этого во время выполнения будет дороже, чем то, что вы пытаетесь предотвратить). В таком языке, как Lisp, где вы управляете временем оценки, быстрый и элегантный способы одинаковы. :-)
— Кен

Развертывание цикла, безусловно, лучше, чем масса операторов if / else. Это одновременно элегантно и функционально .
— Randolpho

3
если вы сделаете первое «если» a «while», оно также будет работать с 64-битными (но даже «некрасиво»). +1
— MartinStettner

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

Разве это не идентично тому, что я опубликовал? Он просто использует [] вместо Substring
— Дина

Нет: он возвращает символ, а не строку, и поиск [] должен быть быстрее
— Джоэл Кохорн

Это правда, но это все тот же базовый метод - анализ строкового представления. Думаю, @Dinah что-то ищет ... ну ... другое.
— Randolpho

@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

Если вы думаете, что ответ Keltex уродлив, попробуйте этот, он ДЕЙСТВИТЕЛЬНО уродливый и даже быстрее. Он выполняет развернутый двоичный поиск для определения длины.

 ... 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: Ах, восторг! Его следует прятать, приносить только для особых случаев и читать приглушенным тоном ... Кто сказал, что у программирования нет души?
— Майк Данлэйви,

как насчет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,
— Мартин

Спасибо, Мартин, я не особо использую математические библиотеки .NET и забыл, что это Log и Log10 вместо Log и Ln.
— mqp

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

1
Кроме того, OP запросил более осмысленное выражение, без учета производительности, и это наиболее прямой (математический) способ выразить то, что требуется.
— harpo

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

Результат в temp


@ck: Какое округление выдано? В данном случае деление - это целочисленное деление, каждое деление точно отсекает последнюю цифру ...
— Мартин Стеттнер,

3

Я знаю, что это не C #, но удивительно любопытно, что в python «получить первый символ строкового представления числа» быстрее!

РЕДАКТИРОВАТЬ : нет, я совершил ошибку, я забыл снова построить 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
$

Но преобразует ли он его обратно в целое число?
— Samuel

упс, теперь это имеет смысл -.-
— 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 )запускает System.OverflowExceptionи - -2147483648вычисляет -2147483648.

Реализацию можно рассматривать как комбинацию преимуществ двух самых быстрых на данный момент реализаций. Он использует двоичный поиск и избегает лишних делений. Быстрый тест с индексом цикла с 100000000 итераций показывает, что он в два раза быстрее, чем самая быстрая реализация на данный момент.

Он заканчивается после 2 829 581 тика.

Для сравнения я также измерил исправленный вариант самой быстрой на данный момент реализации, который занял 5 664 627 тиков.

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
А еще есть 10 тысяч, 100 тысяч, 1 мил, 10 мил, 100 мил и так далее?
— Павел Краковяк,

Я бы так и поступил. Намного эффективнее, чем эта рекурсивная функция.
— Keltex

Этот ответ - лучший из всех возможных.
— Брайан,

Эффективность куплена за счет действительно действительно ужасного, повторяющегося, неподдерживаемого кода. Что, если вы случайно пропустили ранг? Упс.
— Randolpho

Вам действительно следует изменить порядок этих условий так, чтобы наиболее вероятные были наверху ...
— Джейсон Пуньон

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 (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 других ответа - было бы хорошо знать, почему нам следует использовать ваш, а не другие.
— Вай Ха Ли

В исходном решении у меня было несколько ошибок с использованием больших чисел. Мое решение чище. Пожалуйста.
— teodoric8.

Ваше решение не подходит для -1.
— Вай Ха Ли

:) используйте это тогда: if (myVal.ToString () [0]! = '-') {....} и - знак не является числом!
— teodoric8.

Почему бы вместо проверки строки не проверить, не является ли число отрицательным?
— Вай Ха Ли

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

Почему вы переходите на десятичный формат? Также пол не нужен.
— MartinStettner,

Это было связано с потенциальными проблемами округления, которые на самом деле не нужны.
— cjk

Должно быть while (i> = 10), иначе это не удастся, если число кратно 10.
— Bill the Lizard

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
— Мартин

> = необходимо. Первая цифра 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.
— Younes

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.