大家好,我是game1024,一名喜欢代码的测试同学,最近在做一些基础的编程题练手,希望通过将解题的过程记录下来,可以帮助自己熟悉一下c++语言各种操作
问题描述
小M在工作时遇到了一个问题,他需要将用户输入的不带千分位逗号的数字字符串转换为带千分位逗号的格式,并且保留小数部分。小M还发现,有时候输入的数字字符串前面会有无用的 0
,这些也需要精简掉。请你帮助小M编写程序,完成这个任务。
测试样例
样例1:
输入:
s = "1294512.12412"
输出:
'1,294,512.12412'
样例2:
输入:
s = "0000123456789.99"
输出:
'123,456,789.99'
样例3:
输入:
s = "987654321"
输出:
'987,654,321'
这个题主要考察的是对字符串操作的熟练能力,不涉及复杂的算法,只不过需要处理一下几种特别的输入情况,保证代码的鲁棒性
- 有整数部分,也有小数部分:
51123.987
- 有整数部分,无小数部分:
521
- 有小数部分,但是没有整数部分:
0.123
- 有无用的0:
00000123.987
解题思路
整体思路
先把说一下我的整体思路,这个题的步骤可以分为
- 去除前导零:首先,我们需要找到字符串中第一个非零字符的位置,并从这个位置开始处理字符串。
- 分离整数部分和小数部分:遍历字符串,找到小数点(如果有),将字符串分为整数部分和小数部分。
- 添加千分位逗号:对于整数部分,从后往前每三位插入一个逗号。
- 合并结果:将处理后的整数部分和小数部分(如果有)合并成最终结果。
1. 去除前导零
既然是输入会有不规范的无用0,那么我们可以第一步将输入的数字做一下规范处理(去除前到0),具体做法是:找到字符串s中的第一个非0数的位置nonzero_pos
,然后截取nonzero_pos ~ s末尾
这段子串
下面是这个逻辑的实现代码,find_first_nonzero
的功能是找到s的第一个非0数位置
。
当然,我并没有去调用substr截取子串,而是通过 s.begin() + first_nonzero_pos
直接从非0位置开始正式的处理
cpp
// 函数功能:找到字符串s的第一个非0数位置(也可以叫offset)
size_t find_first_nonzero(const std::string &s) {
size_t pos = 0;
for (auto it = s.begin(); it != s.end(); it++, pos++) {
if (*it == '0') {
continue;
} else {
break;
}
}
return pos;
}
std::string solution(const std::string &s) {
// 找到字符串s的第一个非0数位置
// 然后由 s.begin() + first_nonzero_pos 直接从第一个非0数开始遍历
size_t first_nonzero_pos = find_first_nonzero(s);
for (auto it = s.begin() + first_nonzero_pos; it != s.end(); it++)
}
}
2. 分离整数部分和小数部分
因为格式化数字的时候,只需要对整数部分进行处理,小数部分是不需要变化的。所以我们可以切出整数部分和小数部分,对整数部分处理完后再与小数部分进行拼接即可得到最终的结果。
由于整数和小数是被.
这个字符分隔的,所以我们可以从左到右扫描逐个字符
- 如果还没遇到'.',那么这个数字属于整数部分
- 如果已经遇到'.',那么这个数字属于小数部分
通过meet_dot
来判断是否已经扫描到了.
cpp
// 从输入的第一个非零数字开始遍历,
// 1. 没遇到 "." 之前,都是整数的部分
// 2. 遇到 "." 之后,都是小数部分
string integer_part = string("");
string decimal_part = string("");
for (auto it = s.begin() + first_nonzero_pos; it != s.end(); it++) {
if (*it == '.') {
meet_dot = true;
}
if (!meet_dot) {
integer_part += *it;
} else {
decimal_part += *it;
}
}
3. 添加千分位逗号
接着就是对整数部分进行格式化的处理了,可以回想一下我们小时候数位数的过程,比如一个数字123456789
,我们会从最右边开始数个、十、百、千、万、十万...。因此参考这个过程,我们可以从右向左扫描整数部分,每3个数字添加一个,
代码如下,这里重点操作是
- 从右向左遍历:利用了std::string::riterator来实现
- 每3个添加一个
,
:利用取余操作符i%3
,i%3
的结果只可能是0,1,2不断循环,当i递增时,每算出0,就代表经历了一个循环
cpp
int i = 0;
string result = string("");
// 从后往前每三位插入一个逗号
for (auto rit = integer_part.rbegin(); rit != integer_part.rend();
rit++, i++) {
if (i != 0 && i % 3 == 0) {
result = *rit + string(",") + result;
} else {
result = *rit + result;
}
}
4. 合并结果
合并操作,直接利用std::string的+
运算符拼接起来就行!
cpp
// 将处理完的整数部分和小数部分拼起来
return result + decimal_part;
完整代码
cpp
#include <iostream>
#include <string>
// 找到第一个非零字符的位置
size_t find_first_nonzero(const std::string &s) {
size_t pos = 0;
for (auto it = s.begin(); it != s.end(); it++, pos++) {
if (*it == '0') {
continue;
} else {
break;
}
}
return pos;
}
std::string solution(const std::string &s) {
using std::string;
// PLEASE DO NOT MODIFY THE FUNCTION SIGNATURE
// write code here
string integer_part = string("");
string decimal_part = string("");
bool meet_dot = false;
size_t first_nonzero_pos = find_first_nonzero(s);
// 从输入的第一个非零数字开始遍历,
// 1. 没遇到 "." 之前,都是整数的部分
// 2. 遇到 "." 之后,都是小数部分
for (auto it = s.begin() + first_nonzero_pos; it != s.end(); it++) {
if (*it == '.') {
meet_dot = true;
}
if (!meet_dot) {
integer_part += *it;
} else {
decimal_part += *it;
}
}
int i = 0;
string result = string("");
// 从后往前每三位插入一个逗号
for (auto rit = integer_part.rbegin(); rit != integer_part.rend();
rit++, i++) {
if (i != 0 && i % 3 == 0) {
result = *rit + string(",") + result;
} else {
result = *rit + result;
}
}
// 将处理完的整数部分和小数部分拼起来
return result + decimal_part;
}
int main() {
std::cout << (solution("1294512.12412") == "1,294,512.12412") << std::endl;
std::cout << (solution("0000123456789.99") == "123,456,789.99") << std::endl;
std::cout << (solution("987654321") == "987,654,321") << std::endl;
}
总结
涉及到的知识有
- std::string操作,包括字符串连接、迭代器操作
- std::string迭代器的使用,
string() + string
、迭代器偏移运算begin()+offset
、逆向迭代器rbegin()
- 每间隔3个处理一次:取余运算符
%3 == 0