int(C#)の最初の桁を取得するにはどうすればよいですか?


82

C#では、intの1桁目を取得するための最良の方法は何ですか?私が思いついた方法は、intを文字列に変換し、文字列の最初の文字を見つけて、それをintに戻すことです。

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

これは仕事をしますが、そのような問題にはおそらく良い、単純な、数学ベースの解決策があるように感じます。文字列の操作が不格好に感じます。

編集:速度の違いに関係なく、Substring()の代わりにmystring [0]は文字列操作にすぎません


これは、再帰的な方法よりもさらに低速です。=)
J. Steen

私の悪い、私はストップウォッチをリセットするのを忘れました=)、それは他のものより本当に遅いです
Quan Mai

2
これは負の数では失敗します。
台座2010

2
intの最初の桁は常にゼロ抑制で1です(01010101010100101011011011100101)LOL
Zachary Scott

回答:



222

ベンチマーク

まず、「最良の」ソリューションの意味を決定する必要があります。もちろん、アルゴリズムの効率、可読性/保守性、および将来バグが発生する可能性を考慮に入れてください。ただし、注意深い単体テストは、一般的にこれらの問題を回避できます。

これらの各例を1,000万回実行したところ、結果の値は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 2009

2
ベンチマークの場合は+1ですが、これは完全にやり過ぎであることに同意します。パフォーマンスが心配な場合は、そもそもC#を使用するべきではありません:)
GrahamS 2009

15
+ 1-素晴らしいもの。これは、コーディングが好きであることを明らかにする、金メッキされたデラックスモデルのやり過ぎです。あなたへの多くの称賛。いいえ、スチュアート、これが無防備な初心者の心を汚染するとは思わない。うまくいけば、それは彼らにソフトウェアへの愛情を育むことを教えるでしょう。
マークブリッティンガム

2
@ Vikash-acharをキャストするとint、文字のコードポイントに0評価されるため、文字をキャストすると48に評価されます。48を減算すると、効果的に整数に変換されます。別の例として、文字を鋳造5するint53と評価、及び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
このようなものの再帰...あなたに恥をかかせます。そこにループを入れます。
ウェルボグ2009年

@Welbog :)、通常はそうします。しかし、私はこの特定の質問について多くの郷愁を持っています。これは、私がCSの割り当てについて尋ねられた最初の質問とほぼ同じです。当時、私は本質的にこのソリューションを作成しました。
JaredPar 2009年

これは、「nice」=遅い場合です
Keltex 2009年

オプティマイザーが最初のバージョンを末尾呼び出しで最適化できるものと見なしてくれることを願っています。その場合、遅くなることはありません。
rmeador 2009年

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を返す)の場合、または値が負の場合、これは機能しません。修正はintnumberOfDigits = 1 + Convert.ToInt32(Math.Floor(Math.Log10(Math.Abs​​(value))));です。
ダンダン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の関数を思い出します。この方法は読むのがかなり醜いですが、基本的にはループ比較を行っており、比較を10程度のグループにまとめていました。これは、最新のCPUが次のグループをより適切に処理できることを知っています。一度に比較します。
meandmycode 2009年

C#にはコンパイル時にこれらの定数を生成する方法がないため、これは素晴らしく醜いだけです(実行時に実行すると、防止しようとしているものよりもコストがかかります)。評価時間を制御するLispのような言語では、高速な方法とエレガントな方法は同じです。:-)
ケン

ループを展開することは、if / elseステートメントのがらくたよりも確かに優れています。エレガントパフォーマンスも優れています。
Randolpho

3
最初の「if」を「while」にすると、64ビットでも機能します(ただし、「醜い」です)。+1
MartinStettner 2009年

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

これは私が投稿したものと同じではありませんか?部分文字列の代わりに[]を使用するだけです
Dinah

いいえ:文字列ではなく文字を返し、[]ルックアップはより高速になるはずです
Joel Coehoorn 2009年

それは本当ですが、それでも同じ基本的な方法です-文字列表現を解析します。@Dinahは何かを探していると思います...あなたが知っている....違う。
Randolpho

@Randolpho:その通りです。よくわからない場合は申し訳ありません。明確にするために質問を更新しました。
ダイナ

また、intを取得するためにfirstDigit-'0 'を返します。(cで動作します... c#で動作すると思います)
Nicolas Irisarri 2009年

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

PSMartinStettnerも同じ考えを持っていました。


@ラッシュ:ああ、携挙!それは隠され、特別な機会のためだけに持ち出され、そして静かな口調で読まれるべきです...プログラミングには魂がないと誰が言いますか?
Mike Dunlavey 2009年

どうi=(...moreugly...)i>=100?i>=1000?i/1000:i/100:i>=10?i/10:i;
David Murdoch

3

明白ですが、遅い数学的アプローチは次のとおりです。

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

時間を計ってください、それはまったく遅くないはずです。これは私の提案であり、ループのない純粋な数学的ソリューションであることを考えると、ここにある多くのソリューションよりもおそらく高速です。現在販売されているすべてのx86プロセッサには、非常に高速で高性能な浮動小数点プロセッサが搭載されていることに注意してください。
Adam Davis

ここでMath.Log10を使用する必要があると思います
MartinStettner 2009年

おかげで、マーティン-私は.NET数学ライブラリをあまり使用せず、Lo​​gとLnではなくLogとLog10であることを忘れました。
mqp 2009年

ええと、アダム、少なくとも10 ^(N-1)(nは桁数)を取る必要があり、さらに対数を取る必要があります。ループする答え(Nを1回除算する場合)と比較すると、少なくとも少し遅いはずだと私には思えます。しかし、私は定期的にベンチマークに驚いています。
mqp 2009年

1
その上、OPはパフォーマンスに関係なく、より意味のある表現を求めました。これは、求められているものを表現するための最も直接的な(数学的な)方法です。
harpo


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

これを少し話題から外すのはIronPythonだったと彼は言うことができます
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ます。

この実装は、これまでの2つの最速の実装の利点の組み合わせと見なすことができます。二分探索を使用し、余分な分割を回避します。100,000,000回の反復を伴うループのインデックスを使用した簡単なベンチマークは、現在最速の実装の2倍の速度であることを示しています。

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

非常に単純です(比較と1つの分割のみが含まれるため、おそらく非常に高速です)。

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などがありますか?
Pawel Krakowiak

私はそれをこのようにします。その再帰関数よりもはるかに効率的です。
ケルテックス2009年

この答えは最高です。
ブライアン

本当に本当に醜い、反復的な、保守不可能なコードを犠牲にして効率を購入しました。誤ってランクをスキップした場合はどうなりますか?おっと。
Randolpho

最も可能性が高いものが一番上になるように、これらの条件を実際に並べ替える必要があります...
JasonPunyon

1

ここで私の同僚の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デュアルコア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のコマンドプロセッサの分岐予測が新しいプロセッサの方が賢いためだと推測できますが、よくわかりません。

私はIntelCPUを持っていません、多分誰かが私たちのためにそれをテストすることができますか?


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

なぜDecimalに変換するのですか?また、床は必要ありません。
MartinStettner 2009年

それは潜在的な丸めの問題のためにありましたが、実際には必要ありません。
cjk 2009年

されるべきである(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))));
}

ログの実装は、ルート検索アルゴリズムを必要とするため、反復的です
Jasper Bekkers 2009年

0

別の方法を提供するために、整数を10で繰り返し除算し、ゼロに達したら1つの値をロールバックすることができます。文字列操作は一般に遅いので、これは文字列操作よりも速いかもしれませんが、決してエレガントではありません。

このようなもの:

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

while条件(curr> 10)を作成すると、prevValueは必要ありません
MartinStettner

> =が必要です。10の最初の桁は10ではなく1である必要があります
Dinah

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

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

これは、内部で同様のループを実装する必要があり、途中で文字列オブジェクトを構築(および解析)する必要があるToString()アプローチよりも効率的です...


0

最後の桁を取得する非常に簡単な方法:

int myInt = 1821;

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

3
それは素晴らしいことですが、問題は最後ではなく最初の桁を取得することです。
ダイナ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());
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.