ファイルの差分を抽出する
先月から今月の間の新規顧客を調べたい、なんてことがあったとしてください。
それぞれの顧客には、独自の顧客コードが割り振られていて、いろんな情報が蓄積されていると思ってください。
その中に、家族に割り振られているコード(当然複数人に割り振られている)、主に支払う人(家族コードと1対1)、住んでいる場所(複数対複数ですな)、というようなデータがあるとします。
顧客データは、月末に全データをタブ区切りで保管してありますので、それぞれの差分を調べると、新規に顧客になって頂いた方がわかるということです。
頭の部分が以下のようなデータが、居酒屋分と食堂分あると思っていただけるとありがたい。
member_id member_name family_code osaifu_meigi tiiki_code
00000663 かおる 000AZ45RR たけさん 022
00000664 あかりさん 000025WEF ごろーちゃん 054
00000665 けんちゃん 000032GTY 下田さん 054
00000666 しんじ 000025WEF ごろーちゃん 054
今回は、どの家族が増えてるか、というのが知りたくて、食堂の場合だけは、どの地域が増えたのか、というのも知りたいという場合です。
その上に、地域の順番は、単純なソートでは無いという状況です。
(東京駅の近くだけは、まとめて見たい、みたいな感じですかね)
ちなみに、今回は、月末に保管するときに、SQLを使うことで、希望の地域の順番で出力することはできています。
つまり、データの順番どおりに新しい家族を拾っていけば良い、ということです。
会社で使ってるのをそのまま公開するのは、ちと難しいので、いろいろと考えてるんですが、しっくりいって無い感はありますな。
まぁ、その辺りは、大人な対応ということで...
ということで、プログラム自体は以下になります。
# -*- coding: utf-8 -*-
# Created on Thu Nov 11 15:08:05 2021
# 新規に登録された家族情報を抽出する
# 家族コード、財布な人、地域コード、で新規かどうかを判断する
# SQLで抽出したデータは、書いてある順番通りに出力することを想定している
#
# GUIで行うこと
# 居酒屋か食堂かを選択
# 新しいファイルと古いファイルを選択
# ファイル作成ボタン
# 作成終了時にポップアップ
# 具体的な作業内容
# 設定ファイルを読み込む(入力フォルダと出力フォルダを定義する)
# 古いファイルで「家族コード、財布な人、地域コード」の集合を作る
# 新しいファイルを1行づつ読む
# 古いファイルに無くて、新家族データに無ければ、新家族データに追加する
# 新地域データの数を増やす
#
import configparser
import PySimpleGUI as sg
import sys
from datetime import datetime
# config.iniがあるかどうか確かめて無かったりしたら終了
try:
fin = open('config.ini','r')
except FileNotFoundError:
sg.popup('プログラムと同じフォルダにconfig.iniが必要',title='注意!!')
sys.exit()
except Exception() as other:
sg.popup('なんか知らんがエラーです ',other,title='注意!!')
sys.exit()
fin.close()
# 設定ファイルを読み込む
cfg = configparser.ConfigParser()
cfg.read('config.ini')
# Windows Layout
layout = [ [sg.T('店の種類選択'),sg.Radio('居酒屋', 'radio', key='izaka',default=True,enable_events = True),sg.Radio('食堂','radio',key='syoku',enable_events = True)],
[sg.In(key='old_file'),sg.FileBrowse('旧ファイル',target='old_file',initial_folder=(cfg['folder']['in']),key='input1')],
[sg.In(key='new_file'),sg.FileBrowse('新ファイル',target='new_file',initial_folder=(cfg['folder']['in']),key='input2')],
[sg.Button('作成',key='go')] ]
# Make Windows Title=新規登録抽出
window = sg.Window('新規登録抽出', layout)
# ファイル選択窓内を消すためにオブジェクトを設定する
ip1 = window['old_file']
ip2 = window['new_file']
# Main loop
while True:
event, values = window.read()
print(event, values)
# これがないと無限ループになります
# プログラムの最後にwindow.close()を書いて終了にする
if event == sg.WIN_CLOSED: #ウィンドウのXボタンを押したときの処理
break
# ラジオボタンが押された場合
if (event == 'izaka') or (event == 'syoku'):
ip1.Update('')
ip2.Update('')
# 作成ボタンが押された場合
if event == 'go':
# 選択したのとは違うファイルを選んでいたら注意を促す
if not((values['izaka'] and values['old_file'].find('居酒屋') > 0 and values['new_file'].find('居酒屋') > 0) or (values['syoku'] and values['old_file'].find('食堂') > 0 and values['new_file'].find('食堂') > 0)):
sg.popup('店種とファイルの関係が間違ってます',title='注意!!')
continue
# 古いファイルで、タブ区切りの「家族コード、財布な人、地域コード」の集合を作る
fin = open(values['old_file'],'r',encoding='shift_jis')
# 1行ずつ読んで集合にする
duml0 = fin.readlines()
duml1 = []
for dd in duml0:
dlist = dd.split('\t')
dlist1 = [dlist[2], dlist[3], dlist[4].zfill(4)]
#print(dlist1)
duml1.append(('\t'.join(dlist1)))
old_data = set(duml1)
fin.close()
# 新しいファイルを1行づつ読む
fin = open(values['new_file'],'r',encoding='shift_jis')
# 1行ずつ読んでリストを作る
duml0 = fin.readlines()
# 新規追加データリストを用意する
new_data = ['family_code osaifu_meigi tiiki_code']
# 食堂の時には、地域用のデータリストと家族数用の辞書も用意する
if values['syoku']:
tiiki_list = []
tiiki_data = {}
for dd in duml0:
dlist = dd.split('\t')
dlist1 = [dlist[2], dlist[3], dlist[4].zfill(4)]
# 古いファイルに無くて、新メンバーデータに無ければ、新メンバーデータに追加する
if ('\t'.join(dlist1) not in old_data) and ('\t'.join(dlist1) not in new_data):
new_data.append(('\t'.join(dlist1)))
# 食堂だったら新地域データの数を増やす
if values['syoku']:
# リストに入っている場合は、辞書の会員数を増やす
if dlist[4].zfill(4) in tiiki_list:
tiiki_data[dlist[4].zfill(4)] += 1
# 入っていない場合は、新しいリストと辞書を作る
else:
tiiki_list.append(dlist[4].zfill(4))
tiiki_data[dlist[4].zfill(4)] = 1
# それぞれのファイルを作成する
# 今の時間を求める
now = datetime.now()
# 居酒屋と食堂で用意するファイルが違う
if values['izaka']:
fout = open(cfg['folder']['out'] + '居酒屋new_member' + now.strftime('%Y%m%d%H%M%S') + '.tsv','w',encoding='shift_jis')
else:
fout = open(cfg['folder']['out'] + '食堂new_member' + now.strftime('%Y%m%d%H%M%S') + '.tsv','w',encoding='shift_jis')
fout1 = open(cfg['folder']['out'] + '食堂tiiki_list' + now.strftime('%Y%m%d%H%M%S') + '.tsv','w',encoding='shift_jis')
# new_dataを書き込む
for dd in new_data:
fout.write(dd + '\n')
fout.close()
# 食堂の場合は、地域データも作成
if values['syoku']:
for dd in tiiki_list:
fout1.write(dd + '\t' + str(tiiki_data[dd]) + '\n')
fout1.close()
sg.popup('作成しました',title='終了')
#
window.close()
このプログラムの勘所
・設定ファイルを使ってみた
毎回好き勝手な設定ファイルを作るよりも、ちゃんとしたフォーマットのファイルを使った方が良い、という、どなたかの教えを守ってみようかと思った次第でございます。
・ラジオボタンを押したらファイル選択欄の文字を消す
そもそも、ラジオボタン押してもイベントが発生しない、と思っていたので、どうしたもんかと困っておりました。
ところが、マニュアルを見ていたら、オプションに「enable_events = True」ってのを設定すると、イベントとして拾ってくれることが分かりました。
ということで、無事に目的の動作をしてくれるようになりましたな。
・古いデータは、存在有無を調べるだけなので、集合にする
ファイルから読む時はリストに入れますが、知りたいのは、そこにあるかどうかだけなので、集合にしてみました。
もしかしたら、早くなってるのかもしれません。
・辞書データを順番どおりに出力するならリストも使う
他にちゃんとした方法があるのかもしれませんが、簡単な方法にしてみました。
順番を覚えておくために、辞書データを作るときに一緒にリストも作ってます。