🦖

桁数の多い数字を3桁ごとにカンマ区切りにする

に公開
2

背景

paizaの問題集をやっていたら、遭遇したのでメモとして残してこうと思います。
【特定の文字で区切り 1 行で出力】大きな数値を 3 けたごとにカンマ区切りで出力 2 C#編(paizaランク C 相当)

言語

C#

考え方

まず、前提として数値としてそのまま扱える場合は、書式フォーマットを使えば簡単に実現できます。

int number = 1000000;
string formattedNumber = String.Format("{0:N0}", number);
Console.WriteLine(formattedNumber);  // 結果: "1,000,000"
とか

int number = 1000000;
string formattedNumber = String.Format($"{number:N0}");
Console.WriteLine(formattedNumber);  // 結果: "1,000,000"
とか

しかし、入力される数値の桁数が、0以上、10^1,000以下とかになると、そのまま数値として扱えなくなります。
例えば以下のような値...

2750977122254194358298125267916896770411727430486800992644844055649144997005709031805143683930126740778356477498740843239833754659855082134670594874696903269342003374753372767825736545421604685404877842018929499915160421174987324693465808187023620183276452941065941134968269423505723444149613183725462809198758444986694127562544577222603493193937934725613740970762762303491686216655445718470228283058308069294546384725757724478850590050321330519965997929294041302568610855595469912784443617858369515144387871648939000419149400337825002917068226171008507335733916043430711170158302641489890342383919202106213483356537752176126623253054979240020602968069419603675256982392379599458591078517812451599800918436942121134692314199036212638170578767803246005410370861481392658

なので、数値ではなく文字列として処理します。
アルゴリズムは、

  1. 入力された数値文字列の長さを取得する
  2. 文字列の長さが3桁以下の場合は、そのまま文字列を返す。
  3. 最初のカンマを打つ位置を計算する。これは、文字列の長さを3で割った余りから判断できる。
    a. 余りが1の場合:最初の1桁の後にカンマ (例: "1,234,567")
    b. 余りが2の場合:最初の2桁の後にカンマ (例: "12,345,678")
    c. 余りが0の場合:最初の3桁の後にカンマ (例: "123,456,789")
  4. 計算された最初のカンマ位置までの部分文字列を取得し、結果文字列に追加する。
  5. 残りの文字列を3桁ずつに分割し、それぞれの前にカンマを付けて結果文字列に追加していく。
  6. 最終的に構築された結果文字列を返す。

実装

using System;
using System.Text;

class Program
{
    static void Main()
    {
        var input = Console.ReadLine();
        if (string.IsNullOrEmpty(input)) // 入力がnullまたは空の場合の考慮
        {
            Console.WriteLine("");
            return;
        }
        var len = input.Length;

        // 3桁以下の場合はそのまま出力 (この処理を追加するとより明確になります)
        if (len <= 3)
        {
            Console.WriteLine(input);
            return;
        }

        // 最初のチャンク(カンマ区切りにおける最初の数値ブロック)の長さを計算する
        // 例: "12345" -> len=5, 5%3=2 なので firstChunkLen=2 (最初のブロックは "12")
        // 例: "123456" -> len=6, 6%3=0 なので firstChunkLen=3 (最初のブロックは "123")
        int firstChunkLen = len % 3;
        if (firstChunkLen == 0)
        {
            firstChunkLen = 3;
        }

        // カンマ区切りに整形された文字列を構築するためのStringBuilder
        StringBuilder formatted = new StringBuilder();

        // 最初のチャンク(数値ブロック)を結果に追加
        formatted.Append(input.Substring(0, firstChunkLen));

        // 残りの部分を3桁ごとに区切り、カンマを挟んで結果に追加する
        for (int i = firstChunkLen; i < len; i += 3)
        {
            formatted.Append(",");
            formatted.Append(input.Substring(i, 3));
        }

        Console.WriteLine(formatted.ToString()); // StringBuilderから文字列に変換して出力
    }
}

まとめ

ここまで大きな数字を扱うことは仕事などではよほどないと思いますが、覚えておくとどこかで使えるかもしれません。

追記

コメントで、BigIntegerでできることを教えていただいたので、実装してみたら、めちゃくちゃシンプルになりました。(しかし、paizaでは、BigIntegerは使えないっぽい...)

using System;
using System.Numerics;

class Program
{
    static void Main()
    {
        var input = Console.ReadLine();
        if (string.IsNullOrEmpty(input)) // 入力がnullまたは空の場合の考慮
        {
            Console.WriteLine("");
            return;
        }

        BigInteger i = BigInteger.Parse(input);
        Console.WriteLine($"{i:N0}");
    }
}

Discussion

YuneKichiYuneKichi

BigIntegerなら、標準の数値書式設定文字列が使えますよ。
あと、区切り文字はカンマとは限らないので、カルチャを指定したほうがよいです。

TakumioooTakumiooo

ありがとうございます!
BigIntegerでできました!数値のカンマ区切りについてはこっちの方が断然いいですね。

区切り文字はカンマとは限らない

こちらも、今回は、問題を解くために固定文字にしましたが、サービス実装する場合は、考慮が必要ですね。勉強になります!