如何获得int(C#)中的第一位数字?


82

在C#中,获取int的第一个数字的最佳方法是什么?我想出的方法是将int转换为字符串,找到字符串的第一个字符,然后将其返回为int。

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

虽然这样做可以完成,但似乎可以找到一个很好的,简单的,基于数学的解决方案来解决这一问题。字符串操作感觉笨拙。

编辑:不管速度差异如何,mystring [0]而不是Substring()仍然只是字符串操作


这甚至比递归方法还要慢。=)
J. Steen

我不好,我忘了重置秒表=),它的速度确实比其他设备慢
Quan Mai 2009年

2
这将导致负数失败。
底座

2
int的第一位数字始终为1,且带有零抑制(010101010101001010110110111001100)大声笑
Zachary Scott 2010年

Answers:



222

基准测试

首先,您必须确定“最佳”解决方案的含义,当然要考虑到算法的效率,其可读性/可维护性以及将来出现漏洞的可能性。但是,仔细的单元测试通常可以避免这些问题。

我将每个示例运行了1000万次,结果值是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


18
这太微不足道了。我感谢您所做的一切,而且内容翔实,但是对于99%的应用程序来说,这太过分了。恐怕一些新手程序员可能会留下不好的印象。(对可读性进行硬核,过早的优化)尽管如此,它仍然很有趣。:)
萨曼莎·布兰纳姆

7
查询表在哪里?
GvS

2
基准测试+1,但我同意这完全是过分的了。如果您担心性能,那么您一开始就不应该使用C#:)
GrahamS

15
+1-好东西。这就是镀金,豪华模型的过大杀伤力,它很明显使您喜欢编码。给您许多荣誉。不,斯图尔特,我认为这不会污染毫无戒心的新手的思想。希望它将教会他们开发软件。
Mark Brittingham 2009年

2
@Vikash -铸造charint的计算结果为字符的码点,所以铸件的字符0的计算结果为48减去48有效地做了转换为整数。作为另一示例,铸造字符5到一个int计算结果为53,和在5从该结果减去48
约翰拉希

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,2009年

不幸的是,当值是1(返回0)或值是负数时,这将不起作用。解决方法是int numberOfDigits = 1 + Convert.ToInt32(Math.Floor(Math.Log10(Math.Abs​​(value))));
DanDan 2011年

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中的一个函数,该方法读起来很丑陋,但它基本上是在进行循环比较,并且将比较分成十组,知道现代cpus可以更好地处理一组一次比较。
meandmycode

这只是非常丑陋,因为C#没有一种在编译时生成这些const的方法(并且在运行时执行此操作比您要阻止的操作要昂贵得多)。在像Lisp这样的语言中,您可以控制评估时间,快速方式和优雅方式是相同的。:-)

展开循环肯定比处理if / else语句更好。它既优雅高效。
Randolpho

3
如果将第一个“ if”设为“ while”,则它也适用于64位(但“丑陋”)。+1
MartinStettner 2009年

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

这与我发布的内容不同吗?它仅使用[]而不是Substring
Dinah 2009年

否:它返回的是字符而不是字符串,并且[]查找应该更快
Joel Coehoorn

是的,但这仍然是相同的基本方法-解析字符串表示形式。我认为@Dinah正在寻找某事...您知道...不同。
Randolpho 2009年

@Randolpho:完全是。对不起,如果我不清楚。我更新了问题以进行澄清。
迪纳2009年

我也将返回firstDigit-'0'来获取int。(它在c中起作用...我猜它在c#
Nicolas Irisarri 2009年

5

与伦纳特(Lennaert)有相同的想法

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 MartinStettner也有同样的想法。


@Rasch:啊,被提!应该将其隐藏起来,仅在特殊情况下使用,并以安静的色调阅读...谁说编程没有灵魂?
Mike Dunlavey,2009年

怎么样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,2009年

谢谢,马丁-我不太使用.NET数学库,忘了它是Log和Log10而不是Log和Ln。
mqp,2009年

好吧,亚当,至少需要取10 ^(N-1)(n为数字)加上对数。与循环答案(您将N除以1倍)相比,在我看来,它至少必须慢一点。但是我经常对基准感到惊讶,所以谁知道呢。
mqp,2009年

1
此外,OP要求在不考虑性能的情况下进行更有意义的表达,这是表达所寻求内容的最直接(数学)方式。
哈波


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
$

但是它将转换回整数吗?
塞缪尔

哇,现在这很有意义-.-
ZeD

好吧,他可以说是使Iron Iron使这个话题变得不太那么重要了
-Ravi

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 )触发aSystem.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)

然后是10k,100k,100万,1000万,1亿等等?
Pawel Krakowiak,2009年

我会这样。比该递归函数有效得多。
2009年

这个答案是最好的。
布赖恩(Brian)2009年

效率是以非常丑陋,重复,不可维护的代码为代价的。如果您不小心跳过了排名怎么办?哎呀
Randolpho 2009年

您真的应该重新排列这些条件,以便最有可能出现在最上面……
Jason Punyon

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

但是可以在AMD FX 8350八核(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

因此,方法5或6是否更快取决于CPU,我只能推测这是因为在新处理器上,CPU的命令处理器中的分支预测更智能,但是我不确定。

我没有任何Intel CPU,也许有人可以为我们测试它?


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个答案-最好知道为什么我们应该使用您的而不是其他人。
李慧夏

使用原始解决方案,我使用了大量的bug。我的解决方案更干净。别客气。
teodoric8。

您的解决方案因失败-1
Wai Ha Lee

:)然后使用它:if(myVal.ToString()[0]!='-'){....}和-号不是数字!
teodoric8。

为什么不检查数字不是负数而不是检查字符串?
Wai Ha Lee

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

为什么要转换为十进制?另外,不需要地板。
MartinStettner,2009年

那里确实存在潜在的舍入问题,实际上并不需要。
cjk

应而(ⅰ> = 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))));
}

Log的实现是迭代的,因为它需要查找根的算法
Jasper Bekkers,2009年

0

只是给您一种选择,您可以将整数重复除以10,然后在达到零时回滚一个值。由于字符串操作通常较慢,因此这可能比字符串操作要快,但绝非优雅。

像这样:

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

设置while条件(curr> 10),则不需要prevValue
MartinStettner,2009年

> =是必需的。10的首个数字应为1,而不是10
Dinah 2009年

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

现在应该工作。还添加了具有陷阱的Abs递归功能。
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
很好,但是问题是要获取第一个数字,而不是最后一个数字。
Dinah 2010年

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

-2
int i = 4567789;
int digit1 = int.Parse(i.ToString()[0].ToString());
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.