danfo.js 空のセルが存在するエクセルファイルを読み込み失敗するなど で SheetJSからセルの値を読み取るときに、セルアドレスを生成するためのコードで、行列を二重にループしていた。 これをもっと簡単に書きたい。
このセルアドレスを作り出す処理は、もし Haskell だったら:
Prelude> f row col = (row,col)
Prelude> pure f <*> [0..5] <*> [0..2]
[(0,0),(0,1),(0,2),(1,0),(1,1),(1,2),(2,0),(2,1),(2,2),(3,0),(3,1),(3,2),(4,0),(4,1),(4,2),(5,0),(5,1),(5,2)]
とすればよい。
JSでも、このような計算はできないのか?
fast-cartesian というライブラリがあったので使ってみます。
$ mkdir celladdress
$ cd celladdress
$ nmp init -y
$ npm install fast-cartesian
$ touch index.js
このライブラリはどうやら ES Modules 方式が必要で、commonJS の require が使えないらしいので、 package.json に以下を追記:
"type": "module",
package.json 全体:
{
"type": "module",
"dependencies": {
"fast-cartesian": "^7.5.0"
}
}
index.js を書きます。
// index.js
import fastCartesian from 'fast-cartesian';
//
// 6行x3列の範囲を読み込む.
//
const rowCount = 6
const colCount = 3
const rowRange = [...Array(rowCount).keys()];
const colRange = [...Array(colCount).keys()];
const allAddresses = fastCartesian([rowRange, colRange]);
console.log(allAddresses)
実行:
$ node index.js
[
[ 0, 0 ], [ 0, 1 ], [ 0, 2 ],
[ 1, 0 ], [ 1, 1 ], [ 1, 2 ],
[ 2, 0 ], [ 2, 1 ], [ 2, 2 ],
[ 3, 0 ], [ 3, 1 ], [ 3, 2 ],
[ 4, 0 ], [ 4, 1 ], [ 4, 2 ],
[ 5, 0 ], [ 5, 1 ], [ 5, 2 ]
]
できました。
では、対象となる前回のエントリーのコードでこれを使おうと思ったのですが、 ライブラリのインポート方式が CommonJS と ES Modules 方式で別々となり面倒。 さらに調査したところ、 CommonJS 方式の同じ機能を持つライブラリがあった cartesian-product、これを使います。
$ npm install cartesian-product
CommonJS方式なので、package.json から以下の記述は削除します。
"type": "module",
もう面倒。Node.js をやめて Deno に移行したい。
まずは、対象となる範囲のセルアドレスを生成します。
const XLSX = require('xlsx')
const DFD = require('danfojs-node');
const product = require('cartesian-product');
//
// 6行x3列の範囲を読み込むために エクセルのアドレスを生成する.
//
const rowCount = 6
const colCount = 3
const rowRange = [...Array(rowCount).keys()];
const colRange = [...Array(colCount).keys()];
const cellAddressList = product([rowRange, colRange]).map( (rowAndCol)=> {
const rowIndex = rowAndCol[0];
const colIndex = rowAndCol[1];
return XLSX.utils.encode_cell( {c:colIndex, r:rowIndex} );
});
console.log(cellAddressList);
実行:
$ node index.js
[
'A1', 'B1', 'C1', 'A2',
'B2', 'C2', 'A3', 'B3',
'C3', 'A4', 'B4', 'C4',
'A5', 'B5', 'C5', 'A6',
'B6', 'C6'
]
うまくいきました。
では、SheetJSでエクセルデータを読み込み、danfo.js で処理する部分のコードを足して完成させます。
const XLSX = require('xlsx')
const DFD = require('danfojs-node');
const product = require('cartesian-product');
//
// 6行x3列の範囲を読み込む.
//
const rowCount = 6
const colCount = 3
const rowRange = [...Array(rowCount).keys()];
const colRange = [...Array(colCount).keys()];
const workbook = XLSX.readFile('maclist-with-empty.xlsx');
const sheetNames = workbook.SheetNames;
const worksheet = workbook.Sheets[sheetNames[0]];
const cellList = product([rowRange, colRange]).map( (rowAndCol)=> {
const rowIndex = rowAndCol[0];
const colIndex = rowAndCol[1];
const cellAddress = XLSX.utils.encode_cell( {c:colIndex, r:rowIndex} );
const cell = worksheet[cellAddress];
if( cell && cell.w ){
return {
text: cell.w,
colIndex: colIndex,
rowIndex: rowIndex};
}
else {
// 値が空のセルは空文字列を割り当てます.
return {
text: '',
colIndex: colIndex,
rowIndex: rowIndex};
}
}).flat();
const head = (list)=>{ return list[0]; }
const tail = (list)=>{ return list.slice(1) };
const column0Values = cellList.filter((cell)=> cell.colIndex==0).map((cell)=> cell.text);
const column1Values = cellList.filter((cell)=> cell.colIndex==1).map((cell)=> cell.text);
const column2Values = cellList.filter((cell)=> cell.colIndex==2).map((cell)=> cell.text);
const tableObject = {};
const df = new DFD.DataFrame({
[head(column0Values)]: tail(column0Values),
[head(column1Values)]: tail(column1Values),
[head(column2Values)]: tail(column2Values)
});
df.print()
実行:
$ node index.js
╔════════════╤═══════════════════╤═══════════════════╤═══════════════════╗
║ │ kind │ name │ price ║
╟────────────┼───────────────────┼───────────────────┼───────────────────╢
║ 0 │ laptop │ MacBook Air │ 98000 ║
╟────────────┼───────────────────┼───────────────────┼───────────────────╢
║ 1 │ laptop │ MacBook Pro │ 248000 ║
╟────────────┼───────────────────┼───────────────────┼───────────────────╢
║ 2 │ │ Mac mini │ 78000 ║
╟────────────┼───────────────────┼───────────────────┼───────────────────╢
║ 3 │ desktop │ iMac │ 154800 ║
╟────────────┼───────────────────┼───────────────────┼───────────────────╢
║ 4 │ desktop │ MacPro │ 715000 ║
╚════════════╧═══════════════════╧═══════════════════╧═══════════════════╝
できました。
Deno で npm のライブラリ使えるようになったので、試してみる。
Denoはインストールされていることが前提になるが、 プロジェクトディレクトリの作成とか初期化とか不要。 いきなりコード書いてよい。
productCellAddresses.js
import product from "npm:cartesian-product";
import XLSX from "npm:xlsx";
const rowCount = 6
const colCount = 3
const rowRange = [...Array(rowCount).keys()];
const colRange = [...Array(colCount).keys()];
const cellAddressList = product([rowRange, colRange]).map((rowAndCol)=> {
const rowIndex = rowAndCol[0];
const colIndex = rowAndCol[1];
return XLSX.utils.encode_cell( {c:colIndex, r:rowIndex} );
});
console.log(cellAddressList);
ちなみに使用した Deno のバージョンはこれ:
$ deno --version
deno 1.28.3 (release, x86_64-unknown-linux-gnu)
v8 10.9.194.5
typescript 4.8.3
実行:
$ deno run productCellAddresses.js
[
"A1", "B1", "C1", "A2",
"B2", "C2", "A3", "B3",
"C3", "A4", "B4", "C4",
"A5", "B5", "C5", "A6",
"B6", "C6"
]
できた。