📌
JavaScriptで数字を桁の配列に変換するベンチマーク
とあるカウンターを作るに当たって、数字を特定桁数(例えば 10 桁)の桁の配列に変換する必要があって、汎用性は微妙で需要零かもしれないけど、いろんな方法を思いついたので、どれが一番速くて簡潔で優雅だろうと思って、色々性能を試してみた。
関数の定義として (m: number, n: number) => (number | null)[]>
が形の関数で、数字を一桁ずつ右揃えで書き、未満の桁(前の零)をnull
にして、普通の数字(零含む)を一桁の数字にするn
要素の配列を返す。
具体的には
f(12, 2) = [1, 2]
f(12, 4) = [null, null, 1, 2]
f(101, 5) = [null, null, 1, 0, 1]
という調子
コードは TypeScript で書かれ、node と bun でそれぞれテストしてみた。Overflow のケースは考慮されていない。
方法一覧
function arrayFrom(m: number, n: number) {
const str = m.toString();
return Array.from({ length: n }, (_, i) =>
i < n - str.length ? null : +str[i - (n - str.length)]
);
}
function arrayExpand(m: number, n: number) {
const str = m.toString();
return [...Array(n - str.length).fill(null), ...str.split('').map(Number)];
}
function arrayFill(m: number, n: number) {
const str = m.toString();
return Array(n - str.length)
.fill(null)
.concat(...str.split('').map(Number));
}
function arrayConcat(m: number, n: number) {
const str = m.toString();
return Array(n - str.length)
.fill(null)
.concat(...str.split('').map(Number));
}
function stringPad(m: number, n: number) {
const str = m.toString();
return str.padStart(n, '-').split('').map((v) => (v === '-' ? null : +v));
}
function mathExtract(m: number, n: number) {
const result: (number | null)[] = [];
while (m > 0) {
result.unshift(m % 10);
m = Math.floor(m / 10);
}
while (result.length < n) {
result.unshift(null);
}
return result;
};
function recursiveExtract(m: number, n: number): (number | null)[] {
if (m === 0 && n === 0) return [];
if (n === 0) return [];
const prev = recursiveExtract(Math.floor(m / 10), n - 1);
prev.push(m === 0 ? null : m % 10);
return prev;
};
テスト結果
結果はすべて四捨五入したもの。node は v16.8.0、bun は v1.0.1。
関数 | テスト1 (node) | テスト2 (node) | テスト3 (bun) | テスト4 (bun) |
---|---|---|---|---|
arrayFrom | 1.301s | 1.310s | 0.459s | 0.460s |
arrayExpand | 0.782s | 0.804s | 0.296s | 0.288s |
arrayFill | 1.030s | 1.043s | 0.500s | 0.493s |
arrayConcat | 1.018s | 1.030s | 0.498s | 0.484s |
stringPad | 0.835s | 0.874s | 0.351s | 0.318s |
mathExtract | 0.963s | 0.964s | 0.625s | 0.599s |
recursiveExtract | 0.962s | 0.967s | 0.378s | 0.367s |
(一番低い値が一番望ましい)
結果分析
いや~bun君速いっす…しかも TypeScript 直実行…すごすぎ
結論として、一番速い方法は arrayExpand
関数で、しかも結構コードが短いくてキレイなので、まあこれ一択かな!
整理しつつエラー検出も付けるとこんな感じになる
function arrayExpand(m: number, n: number) {
if (n <= 0) throw new Error('n must be positive');
if (m < 0) throw new Error('m must be positive or zero');
if (m === 0) return Array(n - 1).fill(null).concat(0);
const str = m.toString();
if (str.length > n) throw new Error('m is too big');
return [...Array(n - str.length).fill(null), ...str.split('').map(Number)];
}
テスト用コード
テスト用コード(すべて)
function arrayFrom(m: number, n: number) {
const str = m.toString();
return Array.from({ length: n }, (_, i) =>
i < n - str.length ? null : +str[i - (n - str.length)]
);
}
function arrayExpand(m: number, n: number) {
const str = m.toString();
return [...Array(n - str.length).fill(null), ...str.split('').map(Number)];
}
function arrayFill(m: number, n: number) {
const str = m.toString();
return Array(n - str.length)
.fill(null)
.concat(...str.split('').map(Number));
}
function arrayConcat(m: number, n: number) {
const str = m.toString();
return Array(n - str.length)
.fill(null)
.concat(...str.split('').map(Number));
}
function stringPad(m: number, n: number) {
const str = m.toString();
return str.padStart(n, '-').split('').map((v) => (v === '-' ? null : +v));
}
function mathExtract(m: number, n: number) {
const result: (number | null)[] = [];
while (m > 0) {
result.unshift(m % 10);
m = Math.floor(m / 10);
}
while (result.length < n) {
result.unshift(null);
}
return result;
};
function recursiveExtract(m: number, n: number): (number | null)[] {
if (m === 0 && n === 0) return [];
if (n === 0) return [];
const prev = recursiveExtract(Math.floor(m / 10), n - 1);
prev.push(m === 0 ? null : m % 10);
return prev;
};
const FUNCTIONS: Record<string, (m: number, n: number) => (number | null)[]> = {
arrayFrom, arrayExpand, arrayFill, arrayConcat, stringPad, mathExtract, recursiveExtract
}
// Generate random numbers
const numbers = Array.from({ length: 1e6 }, () => Math.floor(Math.random() * 1e6));
const forTesting = numbers.slice(0, 10);
const testedResult: Record<number, Record<string, (number | null)[]>> = {};
for (const num of forTesting) {
testedResult[num] = {};
for (const [name, fn] of Object.entries(FUNCTIONS)) {
testedResult[num][name] = fn(num, 10);
}
}
for (const [num, result] of Object.entries(testedResult)) {
// Assert every result is the same
const results = Object.entries(result);
const ref = results[0][1];
for (const [name, res] of results) {
console.assert(JSON.stringify(res) === JSON.stringify(ref), name, num);
}
}
for (const [name, fn] of Object.entries(FUNCTIONS)) {
console.time(name);
for (const num of numbers) {
fn(num, 10);
}
console.timeEnd(name);
}
Discussion