家、ロッカーのドア、ホテルの部屋などに番号を付けるために使用される金属製の数字を販売していると想像してください。顧客がドア/家に番号を付ける必要がある場合、出荷する各数字の数を見つける必要があります。
- 1~100
- 51~300
- 1 から 2,000、左側にゼロ
明らかな解決策は、最初の数字から最後の数字までループを実行し、カウンターを左にゼロがある場合とない場合の文字列に変換し、各桁を抽出して、それをインデックスとして使用して 10 個の整数の配列をインクリメントすることです。
整数範囲全体をループすることなく、これを解決するより良い方法があるのではないかと思います。
任意の言語または疑似コードでのソリューションを歓迎します。
編集:
Answers review
John at CashCommonsとWayne Conradは、私の現在のアプローチは適切で十分に高速であるとコメントしています。ばかげた例えを使ってみましょう: チェス盤のマスを 1 分以内で数えるタスクが与えられた場合、マスを 1 つずつ数えることでタスクを完了することができますが、より良い解決策は、面と面を数えることです。後で建物内のタイルを数えるように求められる可能性があるため、乗算を行います。
Alex Reisnerは、残念ながら、この問題には関係ないように思われる非常に興味深い数学的法則を指摘しています。
Andresは、私が使用しているのと同じアルゴリズムを提案していますが、部分文字列ではなく %10 操作で数字を抽出しています。
CashCommonsのジョンとphordは、必要な桁数を事前に計算し、それらをルックアップ テーブルに格納するか、生の速度のために配列に格納することを提案しています。これは、固定された絶対的な移動不可能な最大整数値がある場合に適したソリューションになる可能性があります。私はそれらのうちの1つを見たことがありません。
高性能マーク とストレーナは、さまざまな範囲に必要な桁数を計算しました。100万の結果は割合があるように見えますが、他の数の結果は異なる割合を示しています.
ストレーナは、10 の累乗である数の桁数を数えるために使用できる数式をいくつか見つけました。
Robert Harveyは、MathOverflow に質問を投稿した非常に興味深い経験をしました。数学者の 1 人が、数学表記法を使用して解を書きました。
アーロノート数学を使用してソリューションを開発し、テストしました。それを投稿した後、彼は Math Overflow に由来する数式を見直し、そこに欠陥を見つけました (Stackoverflow を指してください:)。
noahlavineはアルゴリズムを開発し、疑似コードで提示しました。
新しい解決策
すべての回答を読み、いくつかの実験を行った後、1 から 10 n -1 までの整数の範囲について次のことがわかりました。
- 1~9の数字はn×10 (n-1)個必要
- 数字 0 の場合、先行ゼロを使用しない場合、n*10 n-1 - ((10 n -1) / 9) が必要です
- 数字 0 の場合、先行ゼロを使用する場合、n*10 n-1 - n が必要です
最初の式はストレーナーによって(おそらく他の人によって)発見され、他の2つは試行錯誤によって発見されました(ただし、他の回答に含まれる場合があります)。
たとえば、n = 6 の場合、範囲は 1 ~ 999,999 です。
- 1 から 9 の数字には、それぞれ 6*10 5 = 600,000 が必要です
- 先行ゼロなしの数字 0 の場合、6*10 5 – (10 6 -1)/9 = 600,000 - 111,111 = 488,889が必要です。
- 数字 0 の場合、先頭にゼロがある場合、6*10 5 – 6 = 599,994が必要です。
これらの数値は、ハイパフォーマンス マークの結果を使用して確認できます。
これらの式を使用して、元のアルゴリズムを改善しました。整数の範囲の最初の数字から最後の数字までループしますが、10 の累乗である数字が見つかった場合は、式を使用して桁数に追加し、1 から 9 までの全範囲の数量をカウントします。または1から99または1から999など。疑似コードのアルゴリズムは次のとおりです。
integer First,Last //範囲内の最初と最後の数字 integer Number //ループ内の現在の番号 integer Power //Power は式の 10^n の n です integer Nines //Nines は 10^n - 1, 10^5 - 1 = 99999 の結果です integer Prefix //数値の最初の桁。14,200 の場合、プレフィックスは 142 です array 0..9 Digits //すべての桁のカウントを保持します FOR Number = 最初から最後まで CALL TallyDigitsForOneNumber WITH Number,1 //各桁のカウントを集計します // 数値を 1 ずつ増やします //最適化の開始。コメントは Number = 1,000 および Last = 8,000 に対するものです。 累乗 = 数字の末尾にゼロ // 1,000 の場合、累乗 = 3 IF Power > 0 //数字の末尾が 0 00 000 など 9 = 10^Power-1 //9 = 10^3 - 1 = 1000 - 1 = 999 IF Number+Nines <= Last //1,000+999 < 8,000 の場合、フルセットを追加 Digits[0-9] += Power*10^(Power-1) //数字 0 ~ 9 に 3*10^(3-1) = 300 を加算 Digits[0] -= -Power //桁 0 を調整します (先行ゼロ式) Prefix = Number の最初の桁 //1000 の場合、接頭辞は 1 です CALL TallyDigitsForOneNumber WITH Prefix,Nines //それぞれの数を集計します //プレフィックスの数字、 //999ずつインクリメント Number += Nines //ループ カウンターを 999 サイクルインクリメントする ENDIF ENDIF //最適化終了 ENDFOR サブルーチン TallyDigitsForOneNumber パラメータ 数値、カウント 繰り返す 桁数 [ 数値 % 10 ] += カウント 数 = 数 / 10 UNTIL番号= 0
たとえば、範囲が 786 から 3,021 の場合、カウンターはインクリメントされます。
- 786 ~ 790 を 1 ずつ (5 サイクル)
- 790 から 799 まで 9 ずつ (1 サイクル)
- 799 から 800 まで 1 つずつ
- 800から899まで99ずつ
- 899 から 900 まで 1 つずつ
- 900から999まで99ずつ
- 999 から 1000 まで 1 つずつ
- 1000年から1999年までの999年まで
- 1999 年から 2000 年までの 1 つずつ
- 2000年から2999年までの999年まで
- 2999年から3000年まで1ずつ
- 3000~3010まで1ずつ(10サイクル)
- 3010 年から 3019 年までの 9 回分 (1 サイクル)
- 3019年から3021年まで1回ずつ(2サイクル)
合計: 28 サイクル 最適化なし: 2,235 サイクル
このアルゴリズムは先行ゼロなしで問題を解決することに注意してください。先行ゼロで使用するには、ハックを使用しました。
先頭にゼロを付けた 700 から 1,000 の範囲が必要な場合は、10,700 から 11,000 のアルゴリズムを使用してから、桁 1 のカウントから 1,000 - 700 = 300 を引きます。
ベンチマークとソースコード
元のアプローチ、%10 を使用した同じアプローチ、およびいくつかの大きな範囲に対する新しいソリューションをテストしたところ、次の結果が得られました。
元の 104.78 秒 %10 で 83.66 10 の累乗で 0.07
ベンチマーク アプリケーションのスクリーンショット: (ソース: clarion.sca.mx )
完全なソース コードを表示したり、ベンチマークを実行したりしたい場合は、次のリンクを使用してください。
- 完全なソース コード ( Clarion内): http://sca.mx/ftp/countdigits.txt
- コンパイル可能なプロジェクトと win32 exe: http://sca.mx/ftp/countdigits.zip
受け入れられた回答
noahlavine の解決策は正しいかもしれませんが、疑似コードをたどることができませんでした。詳細が欠落しているか、完全に説明されていないと思います。
Aaronaught のソリューションは正しいようですが、コードが複雑すぎて私の好みには合いません。
私はストレイナーの答えを受け入れました。なぜなら、彼の考え方が私をこの新しいソリューションの開発に導いたからです。