从一道Neusoft题中想到的IO和Comparator

    Neusoft内部定期举行考试,给一到题目,限期完成,进行评定,想必每个Neusofter对此都很有体会。话说某年Neusoft某分公司某研发部有这样一道考试题,原题是这样叙述的:

    某公司为其它公司做技术服务,人员按照客户要求出差外派。补贴是在人员出差前预先派发的。需要计算出每个人的补贴数值,并且需要派出日期先后排序,以便于安排进行统一借款并进行补贴的派发。如果派出日期相同,则按照补贴金额从少到多排序。
    按照出差时间长短,补贴的标准是不同的。具体规定是:
    30天以内,每日补贴50元;超出31而在60天以内部分,每日补贴多10元,即60元;超出61而在90天以内部分,每日补贴再多10即70元,……以30日为周期以此类推。
    出差的天数以自然日计算,不需要考虑节假日。
    举例说明:
    张三2010-9-16外派出差,到2010-9-30回到公司,计算出差时间为15天,因为少于30天,出差补贴为50*15=750元。
    李四2010-9-1外派出差,到2010-10-20回到公司,计算出差时间为50天,50*30+60*20=2700元。
    为了方便后期调整出差补贴标准,需要采用config.properties对上面的补贴标准进行配置,程序运行时从C:\test\下读取。
    配置文件的内容为:
base=50
step=10
    给出的输入文件为C:\test\src.txt,每行内容为3部分,姓名 派出日期 释放日期
    其中:每个字段中间以一个空格分隔,日期的形式为2010-9-17。
    结果请写入C:\test\result.txt中,每行内容为5部分:姓名 派出日期 释放日期 出差天数 补助金额。
    其中:每个字段中间以一个空格分隔,日期的形式为2010-9-17(注:月份或日期位数不满2位的,不需要以0补全2位,即2010-9-1不需要输出为2010-09-01);出差天数、金额保留到整数位。
    输入、输出文件编码方式都使用GBK。
    提示:编程过程中,可以使用apache commons包中的api (这个建议与考查的内容无关,至少便于对处理文件关闭进行处理,评分是不会有任何影响)
除以上包以外,请使用j2se5.0或6.0的标准内容。引入其他第3方库并不符合考试要求。


    看完试题,我先忽略Apache的Commons组件吧,既然不参与评分,那还是全部J2SE基础编程吧。整体流程就是从一个文件读取信息,处理这个信 息,然后再写到另外一个文件中。简单的IO操作可以完成。要根据配置文件的信息计算,那么就要先加载配置文件,也是简单的IO操作。再者是对出差补贴的计 算,简单的数列求和计算,因为是分段函数,那么每个区间点的基数就是数列,而区间内的数值就是一个函数。对结果要求进行条件排序,很自然想到 Comparator的使用。还有一个小点就是对日期格式的处理。仅此而已。
    既然要求使用Java,就要使用面向对象的方法来解决,先看看给出的源文件src.txt:

张三 2010-9-17 2010-10-15
李四 2010-9-5 2010-10-30
王五 2010-9-20 2010-11-2
赵六 2010-10-2 2010-10-30
阿童木 2010-10-15 2010-12-31

    那么先创建一个bean来描述一下出差对象吧,按照要求,还要有出差天数和补贴。这个类很简单,如下设计:

import java.util.Date;
public class BusinessTrip {
private String userName;
private Date startDate;
private Date endDate;
private String days;
private Integer allowance;
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
public Date getStartDate() {
return startDate;
}
public void setStartDate(Date startDate) {
this.startDate = startDate;
}
public Date getEndDate() {
return endDate;
}
public void setEndDate(Date endDate) {
this.endDate = endDate;
}
public String getDays() {
return days;
}
public void setDays(String days) {
this.days = days;
}
public Integer getAllowance() {
return allowance;
}
public void setAllowance(Integer allowance) {
this.allowance = allowance;
}
}


    字段都很简单,不做任何说明了。只要注意用来排序的字段要用实现了Comparable接口的类,这样才能调用它们的compareTo()方法进行排序。因为要使用Comparator,那么需要一个编写一个Comparator,如下:

import java.util.Comparator;
public class ComparatorStartDate implements Comparator<BusinessTrip> {
@Override
public int compare(BusinessTrip trip0, BusinessTrip trip1) {
// 升序排序
int flag=trip0.getStartDate().compareTo(trip1.getStartDate());
if(flag==0){//如果派出日期相同,则按照补贴金额从少到多排序。
return trip0.getAllowance().compareTo(trip1.getAllowance());
}
else{
return flag;
}
}
}


    这里做个简单说明,要进行排序,那么排序方法类就要实现Comparator接口,定义泛型后在覆盖的方法参数就可以直接使用该类,而用再强转 Object了。还要记得一点,对List排序时是对List本身包含元素的排序还是对其中对象的某个属性排序。当然这里我们会用List封装 BusinessTrip,那么是对BusinessTrip的某属性排序,所以此处定义泛型是BusinessTrip,而不是List。
    剩下就是计算方法的类了,这里我没有继续分开文件的读取和写入操作,而是一起写入了计算类。我们首先来看配置文件的加载,因为要求要从C盘下某路径下载, 只能使用Properties来解决,不能用ResourceBundle了,因为后者直接加载类路径下的配置文件。而对配置文件的加载一般是在静态块内 完成比较好,如下:

private static int base;
private static int step;
/**
* 加载属性配置文件
*/
static {
InputStream is
= null;
Properties prop
= new Properties();
try {
is
= new FileInputStream("C:\\test\\config.properties");
prop.load(is);
}
catch (FileNotFoundException e) {
e.printStackTrace();
}
catch (IOException e) {
e.printStackTrace();
}
finally {
try {
if (is != null) {
is.close();
}
}
catch (IOException e) {
e.printStackTrace();
}
}
base
= Integer.parseInt(prop.getProperty("base"));
step
= Integer.parseInt(prop.getProperty("step"));
}


    注意IO操作的规范性就行了,下面是加载文件和写入文件的方法,针对此例,做出如下设计:

/**
* 从文件中加载信息
*
*
@param fileName
*
@return
*/
public static List<String> readFromFile(String fileName) {
final File file = new File(fileName);
List
<String> list = new ArrayList<String>();
if (file.exists()) {
BufferedReader input
= null;
try {
input
= new BufferedReader(new InputStreamReader(
new FileInputStream(file), "GBK"));
String line
= null;
while ((line = input.readLine()) != null) {
list.add(line);
}
}
catch (FileNotFoundException e) {
e.printStackTrace();
}
catch (IOException e) {
e.printStackTrace();
}
finally {
try {
if (input != null) {
input.close();
}
}
catch (IOException e) {
e.printStackTrace();
}
}
}
return list;
}


    因为数据是一行一行出现的,那么用List封装比较合适,所以读取时就直接封装到List里面了。下面是写入操作:

/**
* 将结果写回文件
*
*
@param list
*
@param fileName
*/
public static void write2file(List<String> list, String fileName) {
BufferedOutputStream output
= null;
try {
output
= new BufferedOutputStream(new FileOutputStream(fileName));
for (int i = 0; i < list.size(); i++) {
output.write((list.get(i)
+ "\r\n").getBytes());
}
output.flush();
}
catch (FileNotFoundException e) {
e.printStackTrace();
}
catch (IOException e) {
e.printStackTrace();
}
finally {
try {
if (output != null) {
output.close();
}
}
catch (IOException e) {
e.printStackTrace();
}
}
}


    还是将List写回到文件,这里读取和写入都是使用的String泛型的List,而我们实际要操作一个对象,这是题目要求的,写回时不能改变已有格式和信息。那么主函数内就要分开操作,也就是循环遍历,这个不难,可难在对补贴的算法上。
    我们来看看补贴的计算:不满30天,就是不到一个周期,只是按天数和基本补助额度算,那必须单独算,后面是要加成的。好,从满30天,即一个周期开始,要加成计算了,那么来看看加成的方式:

30~60天:period=1 50*30+(day-30)*60
60~90天:period =2 50*30+30*60+(day-60)*70
90~120天:period =3 50*30+60*30+70*30+(day-90)*80
120~160天:period =4 50*30+60*30+70*30+80*30+(day-120)*90

    可以发现,每个周期内的零碎计算是很好算的,第一周期时比较特殊,这里base是50,step是10,那么不足一个周期的补助是(day - 30) * (base + step),后面周期的则是:(day - 30 * period)* (base + step + step * (period - 1)),那么还需要再分开判断一次。也就是按照period为0,为1,和大于2时计算。
    比较难的是满整周期的算法,可以看出它们的规律,都是30天乘以本周期的加成数。这是个数列,分别是30*(50),30*(50+60),30* (50+60+70),30*(50+60+70+80),30*(50+60+70+80+90),因为第一期内的要提出分开算,那么我们从 period=2开始算,30是天数,固定,50是基数,就是base,那么这个从第二项开始的数列就 是:2base+10,3base+30,4base+60,5base+100。又可以发现base的系数和周期数相同,变化的则是后面的数值,那么定 义一个新数列b2=10,b3=30,b4=60,b5=100,也就是b3=b2+20,b4=b3+30,b5=b4+40,这是个递归数列,通项公 式中还有一部分是等差数列。求和很简单了,写出通项公式:bn-1=bn-2+(n-2)*10,bn=bn-1+(n-1)*10。那么使用相加消除 法,就可以得到bn=period * step * (period - 1) / 2,这样就解决了补贴算法问题。整个方法如下:

/**
* 测试方法
*
*
@param args
*
@throws Exception
*/
public static void main(String[] args) throws Exception {
long starttime = System.currentTimeMillis();
List
<String> list = readFromFile("C:\\src.txt");
List
<BusinessTrip> tripList = new ArrayList<BusinessTrip>();
DateFormat df
= new SimpleDateFormat("yyyy-M-d");
List
<String> resList = new ArrayList<String>();
// 读取文件信息,拼装成对象
for (int i = 0; i < list.size(); i++) {
BusinessTrip trip
= new BusinessTrip();
String item
= (String) list.get(i);
String[] items
= item.split(" ");
trip.setUserName(items[
0]);
trip.setStartDate(df.parse(items[
1]));
trip.setEndDate(df.parse(items[
2]));
tripList.add(trip);
}
// 处理对象
for (int i = 0; i < tripList.size(); i++) {
BusinessTrip work
= tripList.get(i);
long time = work.getEndDate().getTime()
- work.getStartDate().getTime();
int day = (int) (time / (60 * 60 * 24 * 1000)) + 1;
int period = (int) (day / 30);
work.setDays(day
+ "");
int allance;
// 核心算法,数列求和是关键
if (period == 0) {
allance
= base * day;
}
else if (period == 1) {
allance
= 30 * base + (day - 30) * (base + step);
}
else {
allance
= 30
* (base * period + period * step * (period - 1) / 2)
+ (day - 30 * period)
* (base + step + step * (period - 1));
}
work.setAllowance(allance);
}
// 进行开始日期的比较
ComparatorStartDate csd = new ComparatorStartDate();
Collections.sort(tripList, csd);
for (int i = 0; i < tripList.size(); i++) {
StringBuffer sb
= new StringBuffer();
BusinessTrip trip
= tripList.get(i);
sb.append(trip.getUserName()).append(
" ").append(
df.format(trip.getStartDate())).append(
" ").append(
df.format(trip.getEndDate())).append(
" ").append(
trip.getDays()).append(
" ").append(trip.getAllowance());
resList.add(sb.toString());
}
write2file(resList,
"c:\\result.txt");
long endtime = System.currentTimeMillis();
System.out.println(endtime
- starttime + " ms");
System.out.println(
"文件已生成!");
}


   这样就完成了出差补贴的计算。关键之处应该是数列的计算,而IO,日期操作,属性加载不应该是困难所在。
    说点题外话:Neusoft这种制度还是不错的,时刻提醒大家要注重的地方,数学是基础,IO是基础,不能老是SSH。这种为员工跳槽着想的做法是值得学习的。其他方面我就不评论了,毕竟我不是Neusoft的。
    一家之言,仅供参考,毕竟没有深入理解出题人的意图,这并不是最佳实践,程序执行时间也仅在25-30ms之间(T7200 2.5G WIN7 JDK6_0_14)。欢迎Neusofter和高手指教。

转载于:https://www.cnblogs.com/nanlei/archive/2011/09/01/2162184.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值