程序控

IPPP (Institute of Penniless Peasent-Programmer) Fellow

  博客园 :: 首页 :: 博问 :: 闪存 :: 新随笔 :: 联系 :: :: 管理 ::

Problem
问题

A large company wishes to monitor the cost of phone calls made by its personnel. To achieve this the PABX logs, for each call, the number called (a string of up to 15 digits) and the duration in minutes. Write a program to process this data and produce a report specifying each call and its cost, based on standard Telecom charges.
有一家大公司希望能对员工的话费进行监控。为了实现这一目标,公司的程控电话交换机将记录每一次拨出的号码(一个不超过15个数字的字符串)以及通话时间。写一个程序来处理这些记录数据,并生成一个报告反应出每次通话的价格(用标准话费计算)。

 

International (IDD) numbers start with two zeroes (00) followed by a country code (1-3 digits) followed by a subscriber's number (4-10 digits). National (STD) calls start with one zero (0) followed by an area code (1-5 digits) followed by the subscriber's number (4-7 digits). The price of a call is determined by its destination and its duration. Local calls start with any digit other than 0 and are free.
国际长途号码(IDD)由两个零作开头(00),后面是国家代码(1-3位数字),再后面是主机号码(4-10位数字)。国内长途(STD)号由一个零开头(0),后面是地区代码(1-5位数字),最后是主机号码(4-7位数字)。话费由拨叫地和通话长度决定。本地号码以非0的数字作为第一位,是免费的。

 

Input
输入

Input will be in two parts. The first part will be a table of IDD and STD codes, localities and prices as follows:
输入包括两个部分,第一部分是IDD和STD的代码表,包括代码、地点和价格,如下所示:

Code △ Locality name$price in cents per minute
代码△地点名称$每分钟价格

where △ represents a space. Locality names are 25 characters or less. This section is terminated by a line containing 6 zeroes (000000).
其中△代表一个空格。地名最长25个字符。这部分输入由6个零表示结束(000000)。

The second part contains the log and will consist of a series of lines, one for each call, containing the number dialled and the duration. The file will be terminated a line containing a single #. The numbers will not necessarily be tabulated, although there will be at least one space between them. Telephone numbers will not be ambiguous.
第二部分是通话日志,由多行组成,每行记录一次通话,包括拨叫的电话号码和时长。全部的输入数据由一个#号表示结束。输入的数字之间至少会有一个空格隔开。电话号码间不存在歧义。

 

Output
输出

Output will consist of the called number, the country or area called, the subscriber's number, the duration, the cost per minute and the total cost of the call, as shown below. Local calls are costed at zero. If the number has an invalid code, list the area as "Unknown" and the cost as -1.00.
输出的数据包括拨叫的号码,目的国家或地区,主机号码,通话时长,每分钟单价以及通话总价,如下所示。本地通话计费为0。如果号码是非法的,则将其目的地区记为“Unknown”,价格为-1.00。

 

Sample input
示例输入

088925 Broadwood$81
03 Arrowtown$38
0061 Australia$140
000000
031526        22
0061853279  3
0889256287213   122
779760 1
002832769 5
#

 

Sample output
输出示例

0         10        20        30        40        50        60
1234567890
12345678901234567890123456789012345678901234567890123456789

031526          Arrowtown                      1526   22  0.38   8.36
0061853279      Australia                    853279    3  1.40   4.20
0889256287213   Broadwood                   6287213  122  0.81  98.82
779760          Local                        779760    1  0.00   0.00
002832769       Unknown                                5        -1.00

译注:原文中的示例输出为一张很不清晰的图片。上面的数据来自我AC的程序输出,格式与原图完全相同。最上面灰色的两行为表头(只作示例参照,不要输出),表示字符的位置。红色的数字表示下面一列数据需要对齐的位置(在下面一列的左侧为左对齐,在右侧为右对齐)。

031526          Arrowtown                      1526   22  0.38   8.36
0061853279      Australia                    853279    3  1.40   4.20
0889256287213   Broadwood                   6287213  122  0.81  98.82
779760          Local                        779760    1  0.00   0.00
002832769       Unknown                                5        -1.00

 

Analysis
分析

最难处理的就是格式,但事实上没有那么麻烦。按照UVa OJ管理员在论坛上的回复,输出时每个字段之间只需以一个或多个空格隔开即可,无需打印成上面的表格样式。此外,在查找号码时要注意,国内长途也可能以00开始,具体是何种长途,要根据号码的长度进行判断。

题目对输入数据的限制很少,因此可能发生的情况非常多,我下面的代码中将所有可能出现异常的情况都用一个非法写入的代码进行检验,如果发生该异常,代码评判的结果将是RE(这是一个很有用的调试技巧,每个做OJ题目的同学都应该熟练掌握)。 每个检验点都有注释,“通过”表示此处未发生异常。你可以把所有检验点都删掉,不影响结果的正确性。

 

Solution
解答

#include <iostream>
#include <map>
#include <string>
#include <vector>
using namespace std;
//存储代码表中的相关信息,包括目的地,价格
struct CODEINFO {string Dest; int Price;};
//存储一个账单项
struct ITEM {string Num; string Dest; string Sub; int Dur; int Price;};
//主函数
int main(void) {
	map<string, CODEINFO> CodeTbl;
	for (string Line; getline(cin, Line); ) {
		if (Line.empty())  *(int*)0 = 0; //输入行不为空:通过
		if (Line == "000000") break;
		size_t iNumEnd = 0;
		//查找地区代码的结束位置
		for (; iNumEnd < Line.length() && isdigit(Line[iNumEnd]); ++iNumEnd);
		if (iNumEnd > 6) *(int*)0 = 0; //地区代码长度小于等于6:通过
		if (Line[iNumEnd] != ' ') *(int*)0 = 0; //地区代码后有空格:通过
		//截取地区代码字符串
		string strNum = Line.substr(0, iNumEnd);
		if (strNum.length() <= 1) *(int*)0 = 0; //地区代码长度大于1:通过
		if (strNum[0] != '0') *(int*)0 = 0;  //地区首数字为0:通过
		//本次输入的地区代码未曾出现:通过
		if (CodeTbl.find(strNum) != CodeTbl.end()) *(int*)0 = 0;
		//查找地区名称结束符(美元符号)
		size_t iDollar = Line.find('$', iNumEnd + 1);
		if (iDollar == Line.npos) *(int*)0 = 0; //输入行包含美元符号:通过
		string strDest = Line.substr(iNumEnd + 1, iDollar - iNumEnd - 1);
		if (strDest.empty()) *(int*)0 = 0; //地区名称不为空:通过
		if (strDest[0] == ' ') *(int*)0 = 0; //地区名首字符不为空格:通过
		//地区名首字符不为空格:通过
		if (strDest[strDest.length() - 1] == ' ') *(int*)0 = 0;
		//地区名称最长25个字符:通过
		if (strDest.length() > 25) *(int*)0 = 0;
		string strPrice = Line.substr(iDollar + 1);
		//地区名称为不能等于Unknow或Local:通过
		if (strDest == "Unknown" || strDest == "Local")  *(int*)0 = 0;
		if (strPrice.empty()) *(int*)0 = 0; //话费不为空:通过
		//话费串全为数字:通过
		for (size_t k = 0; k < strPrice.size(); ++k) {
			if (!isdigit(strPrice[k])) *(int*)0 = 0;
		}
		int nPrice = 0;
		for (size_t k = 0; k < strPrice.length(); ++k) {
			nPrice = nPrice * 10 + strPrice[k] - '0';
		}
		if (nPrice >= 10000) *(int*)0 = 0; //话费小于10000:通过
		CodeTbl[strNum].Dest = strDest;
		CodeTbl[strNum].Price = nPrice;
	}
	vector<ITEM> Report;
	for (string strNum, strDur; cin >> strNum && strNum != "#";) {
		cin >> strDur;
		//号码及时长均不为空:通过
		if (strNum.empty() || strDur.empty()) *(int*)0 = 0;
		//号码字符串全为数字:通过
		for (size_t i = 0; i < strNum.length(); ++i) {
			if (!isdigit(strNum[i])) *(int*)0 = 0;
		}
		//电话码号长度不超过15:通过
		if (strNum.length() > 15) *(int*)0 = 0;
		//时长字符串全为数字:通过
		for (size_t i = 0; i < strDur.length(); ++i) {
			if (!isdigit(strDur[i])) *(int*)0 = 0; //通过
		}
		if (strDur[0] == '0') *(int*)0 = 0; //时长第一个数字不为0:通过
		int nDur = 0;
		for (size_t k = 0; k < strDur.length(); ++k) {
			nDur = nDur * 10 + strDur[k] - '0';
		}
		if (nDur == 0) *(int*)0 = 0; //时长不为0:通过
		if (nDur >= 10000) *(int*)0 = 0; //时长小于10000:通过
		ITEM NewItem = {strNum, "Unknown", strNum, nDur, 0};
		if (strNum[0] == '0') { //长话
			//截取不同长度的地区代码进行查找
			for (int i = 2, nNumLen = (int)strNum.length(); i <= 6; ++i) {
				//如果第2个数字不为0,或截取长度为2,必为国内长途代码
				int nMaxSubLen = (strNum[1] != '0' || i == 2) ? 7 : 10;
				if (nNumLen - i >= 4 && nNumLen - i <= nMaxSubLen) {
					string strCode = strNum.substr(0, i);
					if (CodeTbl.find(strCode) != CodeTbl.end()) {
						NewItem.Dest = CodeTbl[strCode].Dest;
						NewItem.Sub = strNum.substr(i);
						NewItem.Price = CodeTbl[strCode].Price;
						break;
					}
				}
			}
		}
		else { //免费市话
			NewItem.Dest = "Local";
		}
		//总话费小于10000:通过
		if (NewItem.Dur * NewItem.Price >= 100000) *(int*)0 = 0;
		Report.push_back(NewItem);
	}
	string Space = "                          "; //空格,对齐格式之用
	for (vector<ITEM>::iterator i = Report.begin(); i != Report.end(); ++i) {
		char szNum[10];
		//将数字格式化为字符串
		sprintf(szNum, "%d", (i->Dur));
		string strDur = szNum;
		sprintf(szNum, "%.2f", (float)(i->Price) / 100.0f);
		string strPrice = szNum;
		sprintf(szNum, "%.2f", (float)(i->Price * i->Dur) / 100.0f);
		string strTotal = szNum;
		if (i->Dest == "Unknown") { //处理非法号码的情况
			i->Sub.clear();
			strPrice.clear();
			strTotal = "-1.00";
		}
		cout << i->Num << Space.substr(0, 16 - i->Num.length()); //号码
		//本地号码的起始位置要提前,以保证15位号码的对齐
		if (i->Dest == "Local") {
			cout << i->Dest << Space.substr(0, 20 - i->Dest.length()); //地点
			cout << Space.substr(0, 15 - i->Sub.length()) << i->Sub;
		}
		else {
			cout << i->Dest << Space.substr(0, 25 - i->Dest.length()); //地点
			cout << Space.substr(0, 10 - i->Sub.length()) << i->Sub;
		}
		cout << Space.substr(0, 5 - strDur.length()) << strDur; //时长
		cout << Space.substr(0, 6 - strPrice.length()) << strPrice; //单价
		cout << Space.substr(0, 7 - strTotal.length()) << strTotal; //总价
		cout << endl;
	}
	return 0;
}
posted on 2010-08-22 17:59  Devymex  阅读(867)  评论(0编辑  收藏  举报