/Files/unixfy/考勤处理程序-MarkYang.pdf
考勤处理程序
Mark Yang
QQ: 591 247 876
Email: goonyangxiaofang@163.com
一个考勤数据最主要的三项就是:
人名 日期 打卡时间
所以我们这里针对这三项数据进行处理。
一、对原始打卡记录集进行预处理
首先,需要对这些考勤记录进行处理,去每个人,每一天的最早和最晚打卡时间,分别作为上班时间和下班时间。
考勤记录中一个人一天可能有0、1、2、3、……个等多种情况的打卡记录,如果有0条记录,那么自然我们找不到;如果有1条记录,那么我们即将其作为上班时间也将其作为下班时间,因为上班时间和下班时间肯定不是一个时间,所以这种情况下肯定存在迟到或早退现象,甚至即迟到又早退;如果有大于等于2个记录,那么我们去其中的最早和最晚时间分别作为上班时间和下班时间,并且比较这里的上班时间和下班时间是否分别早于和晚于规定的上班时间和下班时间,以此来判断是否迟到或早退。
我们的程序首先要解决的问题就是要从一堆考勤记录中,找到每个人每天最早和最晚的那两个时间。
考勤记录示例:
AAA 20120701 112933
AAA 20120703 143838
AAA 20120703 173006
AAA 20120703 173011
BBB 20120704 082317
BBB 20120704 084932
BBB 20120704 090709
BBB 20120704 100721
BBB 20120704 101016
CCC 20120704 174635
这里,我们考虑一种更为灵活的方式,就是这些记录有可能被打散了,也就是说,记录并不是按照人名、日期排列的,而是一个个之间没有次序关联的“人名 日期 打卡时间”独立单元。
这里采用人名+日期来划分打卡记录,读取数据的时候,按照人民+日期来划分。
另外,这里为了后续的处理方便,我们顺便得到人名集合和日期集合。
程序实现如下:
#include <iostream>
#include <fstream>
#include <sstream>
#include <map>
#include <set>
#include <vector>
#include <string>
using namespace std;
int main()
{
ifstream fin("kaoqin.txt");
ofstream fout("1.txt");
ofstream fout_names("names.txt");
ofstream fout_dates("dates.txt");
if (!fin || !fout || !fout_names || !fout_dates)
{
cerr << "File error!" << endl;
exit(1);
}
map<string, vector<string> > checkins;
set<string> names, dates;
string line;
string name, date, time;
while (getline(fin, line))
{
istringstream sin(line);
sin >> name >> date >> time;
names.insert(name);
dates.insert(date);
checkins[name + '\t' + date].push_back(time);
}
string earliest, lastest;
for (map<string, vector<string> >::const_iterator cit = checkins.begin(); cit != checkins.end(); ++cit)
{
earliest = lastest = cit->second[0];
for (vector<string>::size_type i = 0; i != cit->second.size(); ++i)
{
if (earliest > cit->second[i])
{
earliest = cit->second[i];
}
else if (lastest < cit->second[i])
{
lastest = cit->second[i];
}
}
fout << cit->first << '\t' << earliest << endl;
fout << cit->first << '\t' << lastest << endl;
}
for (set<string>::const_iterator cit = names.begin(); cit != names.end(); ++cit)
{
fout_names << *cit << endl;
}
for (set<string>::const_iterator cit = dates.begin(); cit != dates.end(); ++cit)
{
fout_dates << *cit << endl;
}
fin.close();
fout.close();
fout_names.close();
fout_dates.close();
return 0;
}
程序得到的结果是每人每天都有两条记录分别对应该人在当天最早的上班时间和最晚的下班时间。如果原始记录集中只有一条记录,那么这条记录既作为最早上班时间也作为最晚下班时间。
另外,得到的结果还有人名集和打卡记录中的日期集。
二、判断每人每天是否迟到或早退
根据第一步得到的结果,每人每天有两条打卡记录,判断第一条记录是否早于上班时间,且第二条记录是否晚于下班时间,如果都符合则说明没有迟到和早退现象,否则存在迟到或早退现象。将出勤正常的记录与出勤不正常的记录分隔开,得到出勤正常结果和出勤不正常结果。
程序实现如下:
#include <iostream>
#include <fstream>
#include <sstream>
#include <string>
using namespace std;
struct checkin
{
string name;
string date;
string time;
};
void foo(ofstream& fout_nor, ofstream& fout_abn, const checkin& ci1, const checkin& ci2, const string& start, const string& end)
{
if (ci1.time <= start)
{
fout_nor << ci1.name << '\t' << ci1.date << '\t' << ci1.time << endl;
}
else
{
fout_abn << ci1.name << '\t' << ci1.date << '\t' << ci1.time << endl;
}
if (ci2.time >= end)
{
fout_nor << ci2.name << '\t' << ci2.date << '\t' << ci2.time << endl;
}
else
{
fout_abn << ci2.name << '\t' << ci2.date << '\t' << ci2.time << endl;
}
}
int main()
{
ifstream fin("1.txt");
ofstream fout_nor("2-normal.txt"), fout_abn("2-abnormal.txt");
if (!fin || !fout_nor || !fout_abn)
{
cerr << "File error!" << endl;
exit(1);
}
string line1, line2;
checkin ci1, ci2;
string start = "083000", end = "173000";
while (getline(fin, line1) && getline(fin, line2))
{
istringstream sin1(line1), sin2(line2);
sin1 >> ci1.name >> ci1.date >> ci1.time;
sin2 >> ci2.name >> ci2.date >> ci2.time;
foo(fout_nor, fout_abn, ci1, ci2, start, end);
}
fin.close();
fout_nor.close();
fout_abn.close();
return 0;
}
程序得到的两个结果文件,一个是正常考勤记录的,一个是迟到或早退的。我们下一步主要针对迟到或早退的记录文件进行处理。
三、对迟到或早退记录进行统计
1)按人名进行组织这些信息,并输出有效的格式
按人名进行组织,在单个人名内部,又按日期进行组织。输出的结果有两种形式,分别是按行的输出以及按列的输出。
程序实现如下:
#include <iostream>
#include <fstream>
#include <sstream>
#include <string>
#include <map>
#include <set>
#include <vector>
using namespace std;
struct date_time
{
string date;
string time;
};
int main()
{
ifstream fin_ci("2-abnormal.txt");
ofstream fout_row("3-name-row.txt");
ofstream fout_col("3-name-column.txt");
if (!fin_ci || !fout_row || !fout_col)
{
cerr << "File error!" << endl;
exit(1);
}
map<string, vector<date_time> > bypers;
vector<string> names;
string line, name;
date_time dt;
int MAXNUM = 0;
while (getline(fin_ci, line))
{
istringstream sin(line);
sin >> name >> dt.date >> dt.time;
bypers[name].push_back(dt);
}
// 同名字下相同日期的合并
for (map<string, vector<date_time> >::iterator cit = bypers.begin(); cit != bypers.end(); ++cit)
{
// 提取名字
names.push_back(cit->first);
vector<date_time> tmp, hold = cit->second;
vector<date_time>::size_type i = 0;
while (i < hold.size() - 1)
{
if (hold[i].date == hold[i+1].date)
{
dt.date = hold[i].date;
dt.time = hold[i].time + hold[i + 1].time;
tmp.push_back(dt);
i += 2;
}
else
{
dt.date = hold[i].date;
dt.time = hold[i].time;
tmp.push_back(dt);
++i;
}
}
// 最后两个如果相等,那么 i 一下子回跳到 hold.size()。
// 如果不相等,那么 i 变成 hold.size() - 1, 推出循环。
// 也就是说推出循环有两种情况,分别是 i 为 hold.size() 和 hold.size() - 1
// i 为 hold.size() 的情况没有遗漏记录,i 为 hold.size() - 1 遗漏了数据,所以在此补充。
if (i == hold.size() - 1)
{
dt.date = hold[i].date;
dt.time = hold[i].time;
tmp.push_back(dt);
++i;
}
// 记录最大记录个数
if (MAXNUM < tmp.size())
{
MAXNUM = tmp.size();
}
cit->second = tmp;
}
// 按行输出
for (map<string, vector<date_time> >::const_iterator cit = bypers.begin(); cit != bypers.end(); ++cit)
{
fout_row << cit->first << '\t';
for (vector<date_time>::size_type i = 0; i != cit->second.size(); ++i)
{
fout_row << cit->second[i].date << '\t' << cit->second[i].time << '\t';
}
fout_row << endl;
}
// 先输出人名
for (vector<string>::size_type i = 0; i != names.size(); ++i)
{
fout_col << names[i] << '\t';
}
fout_col << endl;
// 按列输出
for (int i = 0; i != MAXNUM; ++i)
{
for (vector<string>::size_type j = 0; j != names.size(); ++j)
{
if (bypers[names[j]].size() > i)
{
fout_col << bypers[names[j]][i].date << ',' << bypers[names[j]][i].time << '\t';
}
else
{
fout_col /* << "XXXXXX" */ << '\t';
// 这里的输出 "XXXXXX" 可以省略,即可以直接 fout_col << '\t';
}
}
fout_col << endl;
}
fin_ci.close();
fout_row.close();
fout_col.close();
return 0;
}
2)按日期进行组织
方法和按人名类似,只要把人名和日期转换一下即可。这里在读取数据的时候,故意把人名和日期进行逆置,其他部分不用修改。
输出还是按行、按列两种方式。
另外,输入文件名和输出文件名相应的修改一下。
程序实现如下:
#include <iostream>
#include <fstream>
#include <sstream>
#include <string>
#include <map>
#include <set>
#include <vector>
using namespace std;
struct date_time
{
string date;
string time;
};
int main()
{
ifstream fin_ci("2-abnormal.txt");
ofstream fout_row("3-date-row.txt");
ofstream fout_col("3-date-column.txt");
if (!fin_ci || !fout_row || !fout_col)
{
cerr << "File error!" << endl;
exit(1);
}
map<string, vector<date_time> > bypers;
vector<string> names;
string line, name;
date_time dt;
int MAXNUM = 0;
while (getline(fin_ci, line))
{
/*
// 这是原来 3-1 的程序,即按照人名组织的。
istringstream sin(line);
sin >> name >> dt.date >> dt.time;
bypers[name].push_back(dt);
*/
// 按照日期来组织
// 将原来的人名看做日期,原来的日期看做人名
istringstream sin(line);
sin >> dt.date >> name >> dt.time; // 要做的只是把 name 与 dt.date 的位置变换一下
bypers[name].push_back(dt);
}
// 同名字下相同日期的合并
for (map<string, vector<date_time> >::iterator cit = bypers.begin(); cit != bypers.end(); ++cit)
{
// 提取名字
names.push_back(cit->first);
vector<date_time> tmp, hold = cit->second;
vector<date_time>::size_type i = 0;
while (i < hold.size() - 1)
{
if (hold[i].date == hold[i+1].date)
{
dt.date = hold[i].date;
dt.time = hold[i].time + hold[i + 1].time;
tmp.push_back(dt);
i += 2;
}
else
{
dt.date = hold[i].date;
dt.time = hold[i].time;
tmp.push_back(dt);
++i;
}
}
// 最后两个如果相等,那么 i 一下子回跳到 hold.size()。
// 如果不相等,那么 i 变成 hold.size() - 1, 推出循环。
// 也就是说推出循环有两种情况,分别是 i 为 hold.size() 和 hold.size() - 1
// i 为 hold.size() 的情况没有遗漏记录,i 为 hold.size() - 1 遗漏了数据,所以在此补充。
if (i == hold.size() - 1)
{
dt.date = hold[i].date;
dt.time = hold[i].time;
tmp.push_back(dt);
++i;
}
// 记录最大记录个数
if (MAXNUM < tmp.size())
{
MAXNUM = tmp.size();
}
cit->second = tmp;
}
// 按行输出
for (map<string, vector<date_time> >::const_iterator cit = bypers.begin(); cit != bypers.end(); ++cit)
{
fout_row << cit->first << '\t';
for (vector<date_time>::size_type i = 0; i != cit->second.size(); ++i)
{
fout_row << cit->second[i].date << '\t' << cit->second[i].time << '\t';
}
fout_row << endl;
}
// 先输出人名
for (vector<string>::size_type i = 0; i != names.size(); ++i)
{
fout_col << names[i] << '\t';
}
fout_col << endl;
// 按列输出
for (int i = 0; i != MAXNUM; ++i)
{
for (vector<string>::size_type j = 0; j != names.size(); ++j)
{
if (bypers[names[j]].size() > i)
{
fout_col << bypers[names[j]][i].date << ',' << bypers[names[j]][i].time << '\t';
}
else
{
fout_col /* << "XXXXXX" */ << '\t';
// 这里的输出 "XXXXXX" 可以省略,即可以直接 fout_col << '\t';
}
}
fout_col << endl;
}
fin_ci.close();
fout_row.close();
fout_col.close();
return 0;
}
3)按照人名,日期进行二维输出
这里有两种方式,分别是“人名-日期”和“日期-人名”。
程序实现是根据人名+日期进行索引找到对应的出勤情况。
程序实现如下:
#include <iostream>
#include <fstream>
#include <sstream>
#include <string>
#include <map>
#include <set>
#include <vector>
using namespace std;
struct date_time
{
string date;
string time;
};
int main()
{
ifstream fin_ci("2-abnormal.txt");
ofstream fout_name_date("3-name-date.txt");
ofstream fout_date_name("3-date-name.txt");
if (!fin_ci || !fout_name_date || !fout_date_name)
{
cerr << "File error!" << endl;
exit(1);
}
map<string, vector<date_time> > bypers;
vector<string> names;
string line, name;
date_time dt;
int MAXNUM = 0;
// 记录日期集合
set<string> dates_tmp;
while (getline(fin_ci, line))
{
istringstream sin(line);
sin >> name >> dt.date >> dt.time;
dates_tmp.insert(dt.date); // 记录日期集合
bypers[name].push_back(dt);
}
// 日期集合
vector<string> dates(dates_tmp.begin(), dates_tmp.end());
// 同名字下相同日期的合并
for (map<string, vector<date_time> >::iterator cit = bypers.begin(); cit != bypers.end(); ++cit)
{
// 提取名字
names.push_back(cit->first);
vector<date_time> tmp, hold = cit->second;
vector<date_time>::size_type i = 0;
while (i < hold.size() - 1)
{
if (hold[i].date == hold[i+1].date)
{
dt.date = hold[i].date;
dt.time = hold[i].time + hold[i + 1].time;
tmp.push_back(dt);
i += 2;
}
else
{
dt.date = hold[i].date;
dt.time = hold[i].time;
tmp.push_back(dt);
++i;
}
}
// 最后两个如果相等,那么 i 一下子回跳到 hold.size()。
// 如果不相等,那么 i 变成 hold.size() - 1, 推出循环。
// 也就是说推出循环有两种情况,分别是 i 为 hold.size() 和 hold.size() - 1
// i 为 hold.size() 的情况没有遗漏记录,i 为 hold.size() - 1 遗漏了数据,所以在此补充。
if (i == hold.size() - 1)
{
dt.date = hold[i].date;
dt.time = hold[i].time;
tmp.push_back(dt);
++i;
}
// 记录最大记录个数
if (MAXNUM < tmp.size())
{
MAXNUM = tmp.size();
}
cit->second = tmp;
}
// 为了下一步输出方便,对数据做一处理
map<string, string> namedate_time;
for (map<string, vector<date_time> >::const_iterator cit = bypers.begin(); cit != bypers.end(); ++cit)
{
for (vector<date_time>::size_type i = 0; i != cit->second.size(); ++i)
{
namedate_time[cit->first + cit->second[i].date] = cit->second[i].time;
}
}
// 按照“人名-日期”进行输出
fout_name_date /* << "XXXXXX" */ << '\t';
for (vector<string>::size_type i = 0; i != dates.size(); ++i)
{
fout_name_date << dates[i] << '\t';
}
fout_name_date << endl;
for (vector<string>::size_type i = 0; i != names.size(); ++i)
{
fout_name_date << names[i] << '\t';
for (vector<string>::size_type j = 0; j != dates.size(); ++j)
{
if (namedate_time.find(names[i] + dates[j]) != namedate_time.end())
{
fout_name_date << namedate_time[names[i] + dates[j]] << '\t';
}
else
{
fout_name_date /* << "XXXXXX" */ << '\t';
}
}
fout_name_date << endl;
}
// 按照“日期-人名”进行输出
// 另一种角度可以将其看成是矩阵的转置
fout_date_name /* << "XXXXXX" */ << '\t';
for (vector<string>::size_type i = 0; i != names.size(); ++i)
{
fout_date_name << names[i] << '\t';
}
fout_date_name << endl;
for (vector<string>::size_type i = 0; i != dates.size(); ++i)
{
fout_date_name << dates[i] << '\t';
for (vector<string>::size_type j = 0; j != names.size(); ++j)
{
if (namedate_time.find(names[j] + dates[i]) != namedate_time.end())
{
fout_date_name << namedate_time[names[j] + dates[i]] << '\t';
}
else
{
fout_date_name /* << "XXXXXX" */ << '\t';
}
}
fout_date_name << endl;
}
fin_ci.close();
fout_name_date.close();
fout_date_name.close();
return 0;
}
四、对一些特殊人名、日期进行剔除的操作
打卡的记录里可能存在节假日的记录,这时需要将这些记录剔除掉。另外,也可能因为某些原因,不考虑某些人的考勤的情况。所以,需要剔除某些指定的人名或日期的考勤打卡记录。
在哪里剔除都可以,较为简便的方法即是在输入数据的时候,就就行筛选,剔除制定的人名或日期记录。
这里只实现一个例子,其他方式也都是一样的,只要在读取数据的时候进行过滤即可。
这里剔除也可以是提取,不管怎样就是去除不想要的记录,得到想要的记录。
程序实现如下:
#include <iostream>
#include <fstream>
#include <sstream>
#include <string>
#include <map>
#include <set>
#include <vector>
using namespace std;
struct date_time
{
string date;
string time;
};
int main()
{
ifstream fin_ci("2-abnormal.txt");
ifstream fin_names("names-filter.txt");
ifstream fin_dates("dates-filter.txt");
ofstream fout_row("3-name-row-filter.txt");
ofstream fout_col("3-name-column-filter.txt");
if (!fin_ci || !fout_row || !fout_col)
{
cerr << "File error!" << endl;
exit(1);
}
map<string, vector<date_time> > bypers;
vector<string> names;
string line, name;
date_time dt;
int MAXNUM = 0;
// 读取想要的人名
set<string> names_filter;
while (fin_names >> name)
{
names_filter.insert(name);
}
// 读取想要的日期
set<string> dates_filter;
while (fin_dates >> line)
{
dates_filter.insert(line);
}
while (getline(fin_ci, line))
{
istringstream sin(line);
sin >> name >> dt.date >> dt.time;
// 提取想要的人名和日期
if (names_filter.find(name) == names_filter.end() || dates_filter.find(dt.date) == dates_filter.end())
{
continue;
}
bypers[name].push_back(dt);
}
// 同名字下相同日期的合并
for (map<string, vector<date_time> >::iterator cit = bypers.begin(); cit != bypers.end(); ++cit)
{
// 提取名字
names.push_back(cit->first);
vector<date_time> tmp, hold = cit->second;
vector<date_time>::size_type i = 0;
while (i < hold.size() - 1)
{
if (hold[i].date == hold[i+1].date)
{
dt.date = hold[i].date;
dt.time = hold[i].time + hold[i + 1].time;
tmp.push_back(dt);
i += 2;
}
else
{
dt.date = hold[i].date;
dt.time = hold[i].time;
tmp.push_back(dt);
++i;
}
}
// 最后两个如果相等,那么 i 一下子回跳到 hold.size()。
// 如果不相等,那么 i 变成 hold.size() - 1, 推出循环。
// 也就是说推出循环有两种情况,分别是 i 为 hold.size() 和 hold.size() - 1
// i 为 hold.size() 的情况没有遗漏记录,i 为 hold.size() - 1 遗漏了数据,所以在此补充。
if (i == hold.size() - 1)
{
dt.date = hold[i].date;
dt.time = hold[i].time;
tmp.push_back(dt);
++i;
}
// 记录最大记录个数
if (MAXNUM < tmp.size())
{
MAXNUM = tmp.size();
}
cit->second = tmp;
}
// 按行输出
for (map<string, vector<date_time> >::const_iterator cit = bypers.begin(); cit != bypers.end(); ++cit)
{
fout_row << cit->first << '\t';
for (vector<date_time>::size_type i = 0; i != cit->second.size(); ++i)
{
fout_row << cit->second[i].date << '\t' << cit->second[i].time << '\t';
}
fout_row << endl;
}
// 先输出人名
for (vector<string>::size_type i = 0; i != names.size(); ++i)
{
fout_col << names[i] << '\t';
}
fout_col << endl;
// 按列输出
for (int i = 0; i != MAXNUM; ++i)
{
for (vector<string>::size_type j = 0; j != names.size(); ++j)
{
if (bypers[names[j]].size() > i)
{
fout_col << bypers[names[j]][i].date << ',' << bypers[names[j]][i].time << '\t';
}
else
{
fout_col /* << "XXXXXX" */ << '\t';
// 这里的输出 "XXXXXX" 可以省略,即可以直接 fout_col << '\t';
}
}
fout_col << endl;
}
fin_ci.close();
fout_row.close();
fout_col.close();
return 0;
}
五、总结
过去的做法:
1. 从考勤记录集合中找到迟到和早退的记录;
2. 针对这些迟到和早退的记录,得到按日期分类的结果集合;
3. 根据第2步的结果得到按照人名-日期的二维表,并且还得到按照人名分类的结果集合。
过去处理这种程序的时候更为零落,一些细节处理的过于繁琐,容易出错。
这里处理的步骤如下:
1. 筛选考勤记录,得到最早和最晚下班时间;
2. 按照规定的上班时间和下班时间得到迟到和早退的考勤记录;
3. 针对迟到和早退的考勤记录,进行统计归纳,分为人名和日期两个维度。另外输出结果分别有按人名行输出、按人名列输出、按日期行输出、按日期列输出、人名-日期输出、日期-人名输出等。
4. 针对特定的人名和日期得到剔除了节假日等情况下的考勤情况。
另外,不区分进、出标识,因为这并没有实际意义。打卡成功与失败不在这里的处理范围之内。
六、附
Excel 的格式问题:
Tab 健可以移位,针对NULL值不写内容(“XXXXXX”)也可以。
空格没有移动功能。
预处理:
这里处理过程中,最为关键的一部是按人名的时候,再按日期进行合并,或者按日期的时候再按人名进行合并。如何合并是一个算法上的问题,比如这样的一个序列 1 2 2 3 3 3,我们想得到1个1、2个2、3个3,并且我们是严格要求同一天或同一个人的需要合并的。所以,需要进行一个排序。
一开始的时候对所有的打卡记录进行排序,按照人名-日期-打卡时间的方式排序。
排序的预处理程序如下:
#include <iostream>
#include <fstream>
#include <string>
#include <vector>
#include <algorithm>
using namespace std;
int main()
{
ifstream fin("kaoqin.txt");
ofstream fout("0.txt");
if (!fin || !fout)
{
cerr << "File error!" << endl;
exit(1);
}
string line;
vector<string> kaoqin;
while (getline(fin, line))
{
kaoqin.push_back(line);
}
sort(kaoqin.begin(), kaoqin.end());
for (vector<string>::size_type i = 0; i != kaoqin.size(); ++i)
{
fout << kaoqin[i] << endl;
}
fin.close();
fout.close();
return 0;
}
posted on 2012-10-25 16:06
unixfy 阅读(494)
评论(0) 编辑 收藏 引用