技術とか戦略とか

IT技術者が技術や戦略について書くブログです。

n対nマッチングのロジック(C#のサンプルコード付き)

以前に、以下の記事にて、マッチング処理のロジックについて書かせていただきました。

マッチング処理のロジック – サイゼントの技術ブログ


以前の記事では1対1マッチングと1対nマッチングについて説明しました。
今回の記事では、より複雑なn対nマッチングについて補足します。


1対1マッチングは、マスタデータの1つのキー項目に対して、トランザクションデータの0~1つのレコードが対応するものでした。
1対nマッチングは、マスタデータの1つのキー項目に対して、トランザクションデータの0~複数のレコードが対応するものでした。
n対nマッチングは、マスタデータ側も1つであるとは限らず、トランザクションデータの1つのキー項目に対して、マスタデータの0~複数のレコードが対応するケースもある、というものを指します。


n対nマッチングでは、以前に参照したトランザクションデータのレコードが、再び参照される可能性があります。
ファイルに対してランダムにアクセスすることでこれを実現できますが、処理が複雑になるため、今回はファイルは順次読み込みのままで、読み込んだトランザクションデータのレコードを一時的に退避するロジックを提示します。


フローチャートと例は以下の通りとなります。


フローチャート

【例】
・要件
商品名が管理されている商品マスタと、商品の販売履歴(トランザクション)をファイル形式で読み込み、商品名と販売日を別ファイルで出力したい。


・商品マスタのフォーマット
カンマ区切りの固定長ファイル。
商品コードと商品副コードでレコードを一意に特定できるようにデータをセットする。

商品コード(7桁)
カンマ(1桁)
商品副コード(2桁)
カンマ(1桁)
商品名(20桁)

・販売履歴のフォーマット
カンマ区切りの固定長ファイル。
商品コード・販売日でレコードを一意に特定できるようにデータをセットする。

商品コード(7桁)
カンマ(1桁)
販売日(8桁)
カンマ(1桁)
販売個数(5桁)
カンマ(1桁)
販売金額(9桁)

・出力ファイルのフォーマット
商品名(20桁)
カンマ(1桁)
販売日(8桁)

・プログラムのフォルダ構成
execute.bat
matching.cs
files┬master.csv
     └transaction.csv

ソースコード(execute.bat)
@echo off

C:\Windows\Microsoft.NET\Framework\v4.0.30319\csc.exe matching.cs
matching.exe
del matching.exe

pause

ソースコード(matching.cs)
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.IO;

namespace Program
{
    class Program
    {
        // EOFフラグ
        static bool isSrmEof = false;
        static bool isSrtEof = false;
        
        static void Main(string args)
        {
            // ファイルオープン
            StreamReader srm = new StreamReader
                (@"files\master.csv", Encoding.UTF8);
            StreamReader srt = new StreamReader
                (@"files\transaction.csv", Encoding.UTF8);
            StreamWriter sw = new StreamWriter
                (@"files\matched.csv", false, Encoding.UTF8);
            
            // 現キー
            string tmpNowKey;
            
            // トランレコード退避用配列
            // COBOLの場合は、十分な長さのOCCURS句を定義する、一時ファイルのOPENとCLOSEを繰り返す、等で対応
            ArrayList tmpTranRecordList;
            
            // 先読みRead
            string
mRecord;
            string tRecord;
            mRecord = mRead(srm);
            tRecord = tRead(srt);
            
            // マッチング処理のループ
            while (!isSrmEof || !isSrtEof)
            {
                // masterのみの場合
                if ((!isSrmEof && isSrtEof) ||
                    (string.Compare(mRecord[0],tRecord[0]) < 0))
                {
                    // 何もしない
                    // master読み込み
                    mRecord = mRead(srm);
                }
                
                // マッチした場合
                else if ((!isSrmEof && !isSrtEof) &&
                         (string.Compare(mRecord[0],tRecord[0]) == 0))
                {
                    // 現キー退避
                    tmpNowKey = mRecord[0];
                    
                    // トランレコード退避用配列初期化
                    tmpTranRecordList = new ArrayList();
                    
                    // transactionが次のキーに進むまでループ
                    while ((!isSrtEof) &&
                           !(string.Compare(tmpNowKey,tRecord[0]) < 0))
                    {
                        // トランレコード退避
                        tmpTranRecordList.Add(tRecord[1]);
                        
                        // transaction読み込み
                        tRecord = tRead(srt);
                    }
                    
                    // masterが次のキーに進むまでループ
                    while ((!isSrmEof) &&
                           !(string.Compare(tmpNowKey,mRecord[0]) < 0))
                    {
                        // 退避したトランレコードを順次結合しファイル出力
                        for (int i = 0; i < tmpTranRecordList.Count; i++)
                        {
                            sw.WriteLine(mRecord[2] + "," + tmpTranRecordList[i]);
                        }
                    
                        // master読み込み
                        mRecord = mRead(srm);
                    }
                }
                
                // transactionのみの場合
                else if ((isSrmEof && !isSrtEof) ||
                         (string.Compare(mRecord[0],tRecord[0]) > 0))
                {
                    // エラー出力
                    Console.WriteLine("Error:" + tRecord[0] + " is tran only.");
                    
                    // transaction読み込み
                    tRecord = tRead(srt);
                }
            }
            
            // ファイルクローズ
            srm.Close();
            srt.Close();
            sw.Close();
        }
        
        // MasterファイルRead
        static string
mRead(StreamReader srm)
        {
            if (srm.Peek() == -1)
            {
                isSrmEof = true;
                return null;
            }
            else
            {
                string str = srm.ReadLine();
                return str.Split(',');
            }
        }
        
        // TransactionファイルRead
        static string[] tRead(StreamReader srt)
        {
            if (srt.Peek() == -1)
            {
                isSrtEof = true;
                return null;
            }
            else
            {
                string str = srt.ReadLine();
                return str.Split(',');
            }
        }
    }
}

・商品マスタのレコード(files\master.csv)
0000001,00,hoge                
0000002,00,fuga                
0000004,01,piyo-Red            
0000004,02,piyo-Blue           
0000005,01,negi-Miku           
0000005,02,negi-Rin            


・販売履歴のレコード(files\transaction.csv)
0000001,20180401,00100,00010000
0000001,20180402,00200,00020000
0000003,20180401,00001,00001000
0000004,20180401,00002,00002000
0000004,20180402,00004,00004000
0000005,20180401,01000,00100000


・バッチ実行結果(標準出力)
Microsoft (R) Visual C# Compiler version 4.8.9032.0
for C# 5
Copyright (C) Microsoft Corporation. All rights reserved.

This compiler is provided as part of the Microsoft (R) .NET Framework, but only supports language versions up to C# 5, which is no longer the latest version. For compilers that support newer versions of the C# programming language, see http://go.microsoft.com/fwlink/?LinkID=533240

Error:0000003 is tran only.
続行するには何かキーを押してください . . .


・バッチ実行結果(files\matched.csv)
hoge                ,20180401
hoge                ,20180402
piyo-Red            ,20180401
piyo-Red            ,20180402
piyo-Blue           ,20180401
piyo-Blue           ,20180402
negi-Miku           ,20180401
negi-Rin            ,20180401

Note IT業界で求められるソフトスキル集 公開のお知らせ

新しい仕事を進める中で、ソフトスキル(コミュニケーションスキル)を体系化して伝えることの難しさを実感しました。
既存の文書では伝えることが難しいとも感じましたので、私の方でNote(ブログ)を新たに作成し、そこにまとめました。

こちらも参考になると思いますので、ご興味がありましたら是非。
右下のリンクにも追加しています。

https://note.com/ses_softskill/

SpringFrameworkのAOPが動かない→spring-boot-starter-aopを取り込んでないだけかも

表題の通りです。ググっても出てこなかったので備忘として残します。

 

ビルドを通すだけであれば"org.aspectj"のみ取り込めば良いのですが、実際にAOPを動かす(JoinPoint・PointCutの条件を満たした時にAspectクラスのメソッドを実行させる)ためには、"spring-boot-starter-aop"も取り込む必要があります。

TypeScript+Node.jsのHelloWorld

TypeScriptは、JavaScriptを拡張して作られた言語です。
 
JavaScriptを大規模開発で採用する場合、実行時のエラーが頻発することで生産性や品質が悪化する恐れがあります。
JavaScriptのこの問題を、ビルド時のエラー判定を強化することで解決した言語が、TypeScriptです。
ビルド時のエラー判定を行った上でJavaScriptのコードを生成するため、JavaScriptとの互換性も高いです。
 
TypeScript独自の代表的な文法の一つが変数の型指定であり、この文法によりビルド時に型相違のエラー判定を行うことができます。
 
今回の記事は、このTypeScriptを、Node.jsを用いてWindows環境上で試しに動かしてみます。
環境構築の参考になれば幸いです。
「とにかくTypeScriptを動かしてどのような挙動になるのか確認したい」という場合にも参考になると思います。
 
■前提
・OS
 Windows11
 
・実施日
 2022年12月29日
 
・Node.jsのバージョン
 18.12.1
 
■Node.jsインストール手順
1.Node.jsの公式サイトにアクセスする。
 https://nodejs.org/ja/
 
2.「18.12.1 LTS」をクリックする。
 ※バージョンはその時々で変化しますが、個人で試すだけなら推奨版で良いです。
 
3.「node-v18.12.1-x64.msi」を実行する。
 
4.Node.jsのインストール画面が出てくるので、デフォルト設定でインストールする。
 ※「Install」押下後、インストールするか尋ねるポップアップが出たら「はい」を押下
 
■Node.jsコマンドプロンプト起動手順
1.「Node.js command prompt」を探して起動。


 
■TypeScriptインストール手順
1.「Node.js command prompt」を起動する。
 
2.下記コマンドを発行し、インストールを実行。
npm install -g typescript
 
3.インストール処理の進捗状況が表示された後、下記のような表示が出ればOK。
added 1 package, and audited 2 packages in 4s

found 0 vulnerabilities
npm notice
npm notice New major version of npm available! 8.19.2 -> 9.2.0
npm notice Changelog: https://github.com/npm/cli/releases/tag/v9.2.0
npm notice Run npm install -g npm@9.2.0 to update!
npm notice
 
■実行確認(Hello World)手順
1.「Node.js command prompt」を起動する。
 
2.下記コマンドを発行し、任意のフォルダ(今回は「C:\tmp\」)に移動する。
cd C:\tmp\
 
3.下記コマンドを発行し、「hello.ts」という名前のファイルを作成する。
type nul>hello.ts
 
4.任意のフォルダに作成した「hello.ts」を、エクスプローラ上から開く。
 ※VS Codeサクラエディタ等の任意のエディタにより開く。
 
5.「hello.ts」に下記(プロンプト上に文字列を出力する命令)を入力して保存。
let msg: string = 'Hello World!';
console.log(msg);
 
6.下記コマンドでTypeScriptのインストール先を確認
npm root -g
 
7.下記コマンドでJavaScriptへ変換。「hello.js」が生成される。
node 6で確認したフォルダパス\typescript\bin\tsc hello.ts
 
8.下記コマンドで「hello.js」を実行、「Hello World!」と出力されればOK。
node hello.js
 
■参考
・生成されたhello.jsは以下の通りであり、JavaScriptの文法への置換を確認できます。
var msg = 'Hello World!';
console.log(msg);

ReactのHelloWorld

フロントエンドのSPA(シングルページアプリケーション)向けのJavaScriptのライブラリであるReactを使用して、HelloWorldを書いてみました。
Reactで何かしらの記述の挙動を確認したい時のベースとして使いやすいようにしています。
 
今回は、下準備無しで動かせるように、CDNサービスを使用しています。
インターネット接続していることが前提となりますが、UNPKGで公開されているパッケージをscriptタグ等で指定することで、ローカルにライブラリを落とすことなくライブラリを使用することができるようになります。
 
SPAらしいHelloWorldになるように、内部状態の変化に応じてHelloWorld!の文字が出たり消えたりするような実装を試しています。
 
なお、ブラウザのバージョンが古くても最新のJavaScriptの書き方で動くように、Babelと呼ばれるライブラリも別途取り込んでいます。
 
■前提
・Reactのバージョン
 v17
 
・Babelのバージョン
 v6
 
■実行確認(Hello World)手順
1.任意のフォルダで"hello.html"という名前のファイルを作成する。
2.「hello.html」に下記を入力してUTF-8で保存。
<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="UTF-8">
  <title>React - Hello world!</title>
  <script 
    crossorigin
    src="https://unpkg.com/react@17/umd/react.development.js"
  ></script>
  <script 
    crossorigin
    src="https://unpkg.com/react-dom@17/umd/react-dom.development.js"
  ></script>
  <script 
    crossorigin
    src="https://unpkg.com/babel-standalone@6/babel.min.js"
  ></script>
</head>
<body>
  <div id="helloContainer"></div>
  <script type="text/babel">
  
    // useStateの取り込み(CDNサービス使用時の宣言)
    const { useState } = React;
  
    const Hello = () => {
      // 状態管理する変数の宣言
      // const [状態変数, 状態を変更するための関数] = useState(状態の初期値);
      const [checkedValues, setCheckedValues] = useState("");
  
      // チェックボックスの状態が変化した場合の処理
      // e.target.valueは状態が変化したチェックボックスが持つ値
      // (今回はボックスを1つしか定義しないため判定に使用する必要はないが、
      //  複数定義する場合はどのボックスの状態が変化したかを判定するのに必要)
      // checkedValuesが初期値なら"Hello World!"をセット、そうでなければ初期値
      const handleChange = (e) => {
        if (e.target.value === "HelloValue") {
          if (checkedValues === "") {
            setCheckedValues("Hello World!");
          } else {
            setCheckedValues("");
          }
        }
      };
      return (
        <label>
          {/* チェックボックスの定義 */}
          <input
            type="checkbox"
            value="HelloValue"
            onChange={handleChange}
          />
          {/* checkedValuesの値を表示 */}
          {checkedValues}
        </label>
      )
    };
    
    const domContainer = document.querySelector("#helloContainer");
    ReactDOM.render(<Hello />, domContainer);
</script>
</body>
</html>
 
3.「hello.html」をブラウザから開く。
 ※今回はChromeで開きます。
 
初期状態では以下のように何も表示されませんが

 
チェックボックスをチェックすると「Hello World!」と表示されます。

 
ボタンをクリックしてイベントを飛ばしたり、リロードしたりすることなく、画面で変更された内部状態をリアルタイムに画面上に反映することができています。

2023年からの更新予定について

来年からこのブログのあり方が変わるため、事前に連絡です。
 
私情なのですが、来年から私の働き方が変わります。
これまでは、自社の若手を率いて客先に常駐したり仕事を請け負ったりする働き方でした。
その中で、若手に教育を行う機会があったため、教育の一環としてこのブログで私の知識やスキルを共有していました。
 
来年からは、私が一人で客先に常駐する働き方となります。
私の技術力が向上したこと、また自社の若手を率いることが無くなることにより、これまでよりも高度な仕事を担うことになる予定です。
 
このブログを開設して以来、定期的にブログを更新する形でアウトプットに努めていましたが、来年からはインプットに使う時間が増えることが予想されます。
そのため、来年からはブログの更新が不定期となる見込みです。
また、これまでは教育のための記事が多かったのですが、今後は自分の思考をまとめるための記事が増える見込みです。
 
このブログの読者の方々の期待に沿わない決断になってしまったかもしれません。
しかし、来年からは違う形で仕事に邁進していきたいと思いますので、見守っていただけると幸いです。

比喩表現により納得感のある説明をする

仕事を進める中で、誰かを説得しないといけない場面があります。
ここで、社内や社外の事情を話したり、論理立てて説明したりすることで納得してくれれば良いのですが、それだけでは納得されにくいこともあります。
 
このような場面で有効な手段の一つが、比喩表現の利用です。
比喩表現を用いて、相手が受け入れやすいものに例えることで、納得感のある説明を行うことができます。
 
----
 
例えば、自分が個人向けオークションサイトの保守開発の上長であるとします。
ここで、部下の一人が、商品に対して高い値付けがされることを快く思っておらず、それを変えることも適切ではないとします。
 
この部下の疑問を解消しなければモチベーションの低下を招くので、なんとかして疑問を解消したい所です。
社内外の事情や論理的な説明で疑問を解消するのが難しい場合、比喩表現を用いて
「これは人力によるダイレクトプライジングのようなものである。ダイレクトプライジングは公的な交通機関でも取り入れられており、今では市民権を得つつある。」
という説明をすることで、受け入れられる可能性が高くなります。
 
なお、今回の記事の趣旨からは外れますが、「自分も昔は同じように思っていた」と部下の疑問に共感を示すことで、より受け入れられやすくなるでしょう。