![見出し画像](https://assets.st-note.com/production/uploads/images/170163646/rectangle_large_type_2_c773dd3e4c8144a7cb8d44774f88fff4.png?width=1200)
計画業務を支える技術 Vol.1 計画業務の裏で動く“賢い表”の仕組み
自己紹介
初めまして、ALGO ARTIS でソフトウェアエンジニアとして従事している古屋佑樹です。
金融業界での銀行のシステムや産業業界のビッグデータ基盤のシステム開発などの経験を経て、今は計画業務の最適化を実行するシステムの裏側で動いている共通基盤の開発を行っております。
はじめに
計画業務の多くはExcel上で行われています。時には複数のファイルを参照したり、マクロを駆使したりして良いと思う計画を組み上げていきます。
私たちのミッションは上記のような計画業務をより効率的に、より直感的にすることにあるのですが、まずは既存の業務ベースで最適化を考えるにあたり、Excelライクな表形式のUIが必要であるとの結論に至りました。
さて、みなさんならある日、表画面を実装してほしいと言われたらどのような実装をするでしょうか。
一緒に考えてみましょう。
表を構成する要素を考えてみる
まず表を構成するにはどのような情報が必要となるのか考えてみましょう。
行と列の情報がそれぞれ必要そうです。またそれぞれ複数の要素を持つ必要がありそうです。
![](https://assets.st-note.com/img/1736825022-HZAhQqalBx4WodtfnuXgMzOb.png)
複数の要素が必要ということで配列(Array)を連想するかもしれません。
配列とは複数の要素を格納できる箱のようなものとイメージしてください。
単一の要素を格納できる箱があるのに対して、複数の要素を格納できる箱が存在しているイメージです。
例えば以下のように表現されます。
単一の要素を表現するA→要素A="バナナ"
複数の要素を表現するArray_A→Array_A=["バナナ","りんご","もも",・・・]
この配列を行と列それぞれで用意してみることにしましょう。
行1:["バナナ","りんご","もも",・・・]
行2:["ブドウ","マンゴー","みかん",・・・]
・
・
・
上記のように繰り返していくことで行と列の情報を持たせることができそうです。
まさにそのものズバリの表現があります。
そうですね、2次元配列です。以下のようなイメージとなります。
![](https://assets.st-note.com/img/1736588964-2fEourJnevCmy3DWpz0gY8wR.png)
上記の2次元配列はarray[行の位置を表現する添字][列の位置を表現する添字]で表現されます。
例えば、1行目かつ1列目はarray[0][0]と表すことができます。
同様に、2行目かつ3列目はarray[1][2]となります。
ここにそれぞれ実際の値を入れていくことで表を表すことが可能です。
array[0][0]: "バナナ" , array[0][1]: "りんご"…
array[1][0]: "ブドウ" , array[0][1]: "マンゴー"…
表を情報として表すことができたので、これを画面上に描画すれば表形式のUI画面を作成できそうです。仕事おわりです!!!
要素追加時の挙動について
・・・と、いきたいところですが、すでに存在する表の行ないしは列に新たに要素を追加したい場合にやや問題があります。
みなさんは、3~4個のボックスを繋げて、落とさないようにボックスを追加したり、抜きだしたり、移動させたりする大道芸は見たことがないでしょうか。(どうやらジャグリングの一種らしい)
1個から2個、2個から3個とボックスを連続して繋げていくのは比較的簡単そうにやるでしょう。
一方で連結したボックスの中間に新たにボックスを入れ込むのは単なる追加に比べて、難しそうです。。
![](https://assets.st-note.com/img/1736589650-dY8bOEQm4yw1WJqxZ0GSPnVs.png)
これと同じことが配列にも当てはまります。
つまり、末尾に要素を追加することは得意としますが、途中に要素を追加するのは末尾の追加に比べてコストがかかるという特性があります。
追加する要素の後続の要素も全て移動させる必要があるためです。
また、同じことは要素の削除にもいえます。
後続の要素を前に移動させる必要があるからです。
これらはいずれも配列でいうところの添字を振り直す操作に相当します。
要素が少ない場合は比較的問題ありませんが、計画業務では扱うデータが大量になることも多いため、このような非効率な処理が UX に大きく影響し、無視できないレベルで悪化する可能性があります。
また、計画業務の特性上、計画を立案・検討する際は「パズルを組み立てる」ように、要素を追加・削除・変更しながら試行錯誤を繰り返す必要があります。そのたびに数秒間待たされるようであれば、UX が非常に悪くなることは想像に難くないかと思います。
すなわち、配列型は末尾のデータ追加は得意だが、中間要素の追加、削除には不得意なのです。
より良い情報構造を求めて
そこでもっと良い情報構造はないか検討してみましょう。
先ほどは挿入した要素だけではなく、関係ない要素を多く変更する必要があるという点がネックでした。
それを避けるためには各要素の位置に関する情報を最低限にすること、すなわち自身の要素の直前の要素および直後の要素はどこかという情報だけ持たせれば良さそうです。
上記の要件を満たせるように要素のそれぞれに直前の要素を示すprevious_idと直後の要素を示すnext_idを持たせましょう。
また配列ではなく、それぞれ独立したオブジェクトとして管理することとします。
すなわち以下のイメージのようになります。これは一般的に双方向リストと呼ばれるデータ構造になります。
![](https://assets.st-note.com/img/1736590391-qYlBOTJzAtrHVF9SyZN7E3gv.png)
要素自身が全体においてどの位置にいるかはわからなくなりましたが、その代わりとして要素の追加、削除の際に全体の順序の整合性を合わせる必要はなく、前後の情報のみ書き換えればよくなりました。
すなわち、要素の追加のイメージは以下のようになります。
配列の場合は追加要素の後続の情報を全て書き換えなくてはならなかったのに対して、前後の要素の情報2箇所のみを書き換えれば良いことがわかります。
イメージ上、あたかも移動させているようにみえますが、それぞれの要素は独立したオブジェクトであり、配列ではないことに注意してください。
![](https://assets.st-note.com/img/1736591689-fEx1aJgdnwOQ0Wo2uNrbS3m8.png?width=1200)
一方で本変更により生まれたデメリットもあります。
要素全体の順番を求めるためには、全データを参照して、チェーンを辿るかのように繋ぎ合わせる必要がある点です。
しかしながら、最適化を行うにあたって必ずしも順番が要求されるかというとそうではありません。
製品名などのマスタはExcel上に表現しているがゆえに順番の情報も保持していますが、順番自体に本質的な意味はなく単にリスト情報として取得できればよいはずです。また、順番が要求されるデータに対しては、先述のようにデータを辿ることで順番を求めることができます。
したがって、我々は当該構造によるデメリットは大きな実害とならないため許容できると判断しました。
これにより配列構造と比較して、効率的に要素を挿入、削除できるようになりました。
基本的には本仕組みをベースに表構造を表現しているのですが、実プロダクト上で変更しているポイントが2点ありますのでご参考までに紹介します。
実プロダクトでの工夫点
1点目は要素の前後の情報をいずれも持たせるのではなく、後続の情報のみを持たせております。
すなわち、previous_idは存在せず、next_idのみ保持しております。
これによりいずれも持たせている場合ではprevious_idが存在しない要素が先頭行と見なすことができましたが、本変更により先頭要素がわからなくなるため、メタ情報として各要素とは別に保存しております。
順番を辿る際にはメタ情報で先頭の要素を参照し、そこからはチェーンを辿るようにnext_idを辿ることで順番を再現することとなります。
本変更により逆方向からの走査が不可となりますが、そのようなオペレーションをしたい動機もなかったため実装をシンプルにする方向で舵を切っております。これは一般的に単方向リストと呼ばれるデータ構造になります。
またこれにより、挿入、削除時には先頭行以外であれば、直前の行の情報のみ書き換えれば良くなり、要素の追加、削除オペレーションが更に効率化されます。
2点目は列の情報は本実装ではなく、単純にソート番号を保持するにとどめております。
これは少し想像してもらえればわかりやすいと思うのですが、みなさんが作る表は行の数>>>>列の数が多いかと思います。
計画業務においても例外ではなく、列数は製品名、保管倉庫名、数量など数列、多くても数十列であるのに対して、行数は数百、数千、数万と圧倒的に多い傾向にありました。
また、列の追加、削除自体、データ構造が変更になる時のみであり頻繁に行うオペレーションではありません。
この事実を踏まえて、列はそれぞれソート番号を保持するにとどめています。厳密には配列構造ではないのですが、要素の追加、削除時には配列と同じことが当てはまります。
すなわち、中間への列の追加、削除時にはソート番号を振り直すことになります。
しかしながら、データ量、頻度ともに小さいため、振り直しの影響は無視できると判断しています。
2次元配列の場合では行、列に対するオペレーションは等価とみなせますが、実態に合わせて設計を変更しているということになります。
まとめ
さていかがでしたでしょうか。
まとめると以下のような情報構造で表構造を表現しております。
・列は要素毎にソート番号を保持して、順番を表現している
・行は直後の行の情報を保持して、順番を表現している
この決定はいくつかの重要な示唆から得られました。
・行と列の追加、削除は頻度、量の観点から等価なオペレーションとする必要はない
・順番の情報は必ずしも重要ではない
雑記
本ブログを執筆するにあたり、あらためて「表構造を支える情報」について想いをめぐらせてみました。
特に面白いなと感じたのはExcelのような表計算ソフトを利用していることで私たちにとって「表をイメージしてデータを捉えること」がごく自然になっている点です。
たとえば、あくまで表現上の都合で行や列を配置しているだけなのに、その順番に本来以上の意味があるように感じてしまったり、本来、実運用では等価ではないはずの行と列のオペレーションを同じように考えてしまったりと、思いもよらない先入観が生まれていることに気づかされました。
ひとつのソフトによって無意識化で情報の捉え方が左右されるというのは純粋にすごいと思う反面、世界の見方も変えてしまうという怖さも感じます。
ChatGPTがAIの代表格として世界に認知され始めていますが、これによりまた世界の捉え方が変わるのかもしれません。
とまぁ、想いが巡って止まらなくなりそうなのでここまでとしておきます。
最後に
ALGO ARTIS では仲間を募集しています!
顧客の計画業務を最適化することをメインミッションとしつつ、それらを支える技術についても日々想いを巡らせ改善しております。
三方よしのシステムができるのが一番望ましいのですが、どの選択が望ましいかはケースバイケースであり、日々チーム内で議論しながら進めております。
頭を悩ませるのが好き、最適なシステムを追い求めたい、新しいことをやりたいといった人にはとてもチャレンジングな環境かと思いますので、興味があればみなさんの応募をお待ちしております。
ALGO ARTIS について:https://www.algo-artis.com/
最適化ソリューション『Optium』:https://www.algo-artis.com/service
化学業界DXソリューション『Planium』:https://planium.jp/
X :https://x.com/algo_artis
Linkedin :https://www.linkedin.com/company/algo-artis/