簡體   English   中英

如何獲得 int (C#) 中的第一個數字?

[英]How can you get the first digit in an int (C#)?

在 C# 中,獲取 int 中第一個數字的最佳方法是什么? 我想出的方法是將int轉成字符串,找到字符串的第一個char,然后再轉回int。

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

雖然這可以完成工作,但感覺可能有一個很好的、簡單的、基於數學的解決方案來解決這樣的問題。 字符串操作感覺很笨拙。

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

基准

首先,您必須確定“最佳”解決方案的含義,當然這要考慮算法的效率、可讀性/可維護性以及將來出現錯誤的可能性。 然而,仔細的單元測試通常可以避免這些問題。

我將這些示例中的每一個運行了 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

就是這樣

int i = Math.Abs(386792);
while(i >= 10)
    i /= 10;

i會包含你需要的東西

嘗試這個

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

我能想到的最好的是:

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

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

安東答案的變化:

 // 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;
int myNumber = 8383;
char firstDigit = myNumber.ToString()[0];
// char = '8'

和Lennaert有同樣的想法

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

這也適用於負數。

如果您認為 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 也有同樣的想法。

我只是偶然發現了這個老問題,並傾向於提出另一個建議,因為到目前為止,其他答案都沒有為所有可能的輸入值返回正確的結果,並且仍然可以更快:

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

這適用於所有簽名的 integer 值,包括-2147483648 ,這是最小的簽名 integer 並且沒有正對應。 Math.Abs( -2147483648 )觸發System.OverflowException並且- -2147483648計算為-2147483648

該實現可以看作是迄今為止兩種最快實現的優點的組合。 它使用二分搜索並避免多余的划分。 具有 100,000,000 次迭代的循環索引的快速基准測試表明,它的速度是當前最快實現的兩倍。

它在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;
}

像這樣的簡單函數可以很容易地證明其正確性,因為在當前硬件上迭代所有可能的 integer 值只需幾秒鍾。 這意味着以非常易讀的方式實現它們並不那么重要,因為以后根本不需要修復它們內部的錯誤。

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

結果在temp

一個明顯但緩慢的數學方法是:

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

我知道這不是 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
$

非常簡單(而且可能很快,因為它只涉及比較和一個除法):

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)

使用下面的所有示例來獲取此代碼:

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的命令處理器中的分支預測在新處理器上更智能,但我不確定。

我沒有任何英特爾 CPU,也許有人可以為我們測試一下?

也檢查一下:

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

在這里與我的一位同事進行了一些測試,發現大多數解決方案不適用於 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 myInt = 1821;

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

這是我通常做的,請參考下面我的 function:

此 function 可以從您可以修改的任何字符串中提取第一個數字出現,並根據您的使用情況使用此 function

   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;

    }

只是為了給您一個替代方案,您可以反復將 integer 除以 10,然后在達到零時回滾一個值。 由於字符串操作通常很慢,這可能比字符串操作更快,但絕不是優雅的。

像這樣的東西:

while(curr>=10)
     curr /= 10;
while (i > 10)
{
   i = (Int32)Math.Floor((Decimal)i / 10);
}
// i is now the first int
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);
}
int start = curr;
while (start >= 10)
  start /= 10;

這比 ToString() 方法更有效,后者在內部必須實現類似的循環,並且必須在途中構造(和解析)字符串 object...

非迭代公式:

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

    return (int)((double)num / Math.Pow(10f, Math.Floor(Math.Log10(num))));
}
int i = 4567789;
int digit1 = int.Parse(i.ToString()[0].ToString());

這是一種不涉及循環的更簡單的方法

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

這將給我們 1234/Math.Pow(10, 4 - 1) = 1234/1000 = 1

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM