# 【C++】競程筆記:字串處理
[TOC]
本筆記僅個人學習用途,斟酌參考。
額...我發現我的字串處理實在是太爛了,所以做這篇來挽救一下我的字串題目。
## String 宣告方式
有四種:
```cpp=
string s1; // 空字串 ""
string s2 = "Hello"; // 初始化為 "Hello"
string s3(s2); // 複製 s2 -> "Hello"
string s4(5, 'A'); // 重複字元 -> "AAAAA"
```
s1 什麼都沒寫,僅宣告的話就是空字串。
s2 賦值 `"Hello"` 字串,則表示 s2 初始化為 `"Hello"`。
`s3(s2)` 括號 `s2` 寫法就是 s3 複製 s2 的 `"Hello"`。
`s4(5, 'A')`,前面是次數,後面是字元,表示重複 5 次 `'A'`。
## 輸入方式
輸入字串的方式有一行一行讀跟整行讀。
一行一行讀就是 `cin`,整行讀就是 `getline(cin, line)`。
使用 `getline(cin, line)` 有個陷阱:
「如果前面已經有寫 `cin >> s` 的話,接下來又用 `getline(cin, s)`,那麼 `cin` 留下的換行符號會被 `getline` 讀去,導致看起來像是『跳過了一次輸入』。」
這個解法就是在兩者之間加一行 `cin.ignore()` 來吃掉那個換行符號。
最後還有一個就是 `cin.get()`,可以讀所有字元,包含換行字元。
## 基本操作
### 取得長度
```cpp=
string s = "Hello";
int len = s.length(); // 或 s.size(),兩者通用
// len 為 5
```
### 字串串接(`+`)
```cpp=
string a = "Happy";
string b = "Coding";
string c = a + " " + b + "!";
// c 變成 "Happy Coding!"
```
### 存取與修改字元
像陣列一樣使用 `[]`。
```cpp=
s[0] = 'h'; // 變成 "hello" (大小寫轉換常用, 只要更改第一個字元即可)
```
### 比較
直接使用 ==, !=, <, > 等運算子(依照字典序比較)。
```cpp=
if (s == "hello") { ... }
if ("apple" < "banana") { ... } // true
```
## 核心功能
### 尋找 `find()`
找子字串或字元的位置。如果找不到,會回傳特殊常數 string::npos。
```cpp=
string s = "Hello World";
size_t pos = s.find("World"); // 回傳 W 的索引值 (6)
if (pos != string::npos) {
cout << "Found at: " << pos << endl;
} else {
cout << "Not found" << endl;
}
```
### 擷取子字串 `substr()`
語法:`s.substr(開始位置, 長度)`
需注意:第二個參數是「長度」,不是結束位置。
```cpp=
string s = "2023-12-25";
string year = s.substr(0, 4); // 從索引 0 開始,抓 4 個字 -> "2023"
string month = s.substr(5, 2); // 從索引 5 開始,抓 2 個字 -> "12"
```
### 插入、刪除、取代
* 插入:`s.insert(位置, "字串")`;
* 刪除:`s.erase(開始位置, 長度)`;
* 取代:`s.replace(開始位置, 長度, "新字串")`;
```cpp=
string s = "I like C++";
s.insert(7, "modern "); // "I like modern C++"
s.erase(2, 5); // 從索引 2 開始刪 5 個字 -> "I modern C++"
s.replace(9, 3, "Python"); // 從索引 9 開始取代 3 個字 -> "I modern Python"
```
## 型態轉換
### 數字轉字串
用 `to_string()`。
```cpp=
int num = 123;
string s = to_string(num); // "123"
```
### 字串轉數字
有四種方法:
* stoi():String to Integer
* stoll():String to Long Long
* stof():String to Float
* stod():String to Double
```cpp=
string a = "123";
int aa = stoi(a);
string b = "123.123";
double bb = stod(b);
```
二進位字串轉整數:`stoi(line, nullptr, 2)`,當中最後面的 2 參數是 line 的進位制。
## 搭配 `<algorithm>` 跟 `<sstream>`
### 字串反轉與排序
使用這些方法前需要引入 `#include <algorithm>`,而且要用 iterator:
- reverse():反轉元素。
- sort():排序
```
string s = "dbca";
reverse(s.begin(), s.end()); // 反轉 -> "acbd"
sort(s.begin(), s.end()); // 排序 -> "abcd"
```
### 字串切割(stringstream)
需要引入 `#include <sstream>`。
當題目輸入是一整行字串,包含未知的空白數量,要把單字一個個切出來時,就可以用 `sstream`。
```cpp=
string line = " This is a pen ";
stringstream ss(line); // 建立一個 string stream
string word;
while (ss >> word) { // 像 cin 一樣讀取,會自動跳過空白
cout << "[" << word << "]";
}
// 輸出:[This][is][a][pen]
```
## `<cctype>`
`#include <cctype>` 引入這個標頭檔。
這邊有幾個好用的函式可以用:
- `isalnum()`:判斷是否為英文字母跟數字。
- `isalpha()`:判斷是否為英文字母。
- `isspace()`:判斷是否有空白。
- `isdigit()`:判斷是否為字母。
- `islower()`:判斷是否為小寫字母。
- `isupper()`:判斷是否為大寫字母。
- `tolower()`:將字元轉成小寫字母。
- `toupper()`:將字元轉成大寫字母。
## 練習題
### ZJ d018. 字串讀取練習
Problem Source:https://zerojudge.tw/ShowProblem?problemid=d018
用 `stringstream` 讀取,至於有 `:` 的部分,可以先用 `int pos = s.find(':')` 找到字元的位置,然後再用 `s.substr(pos + 1)` 跟 `s.substr(pos - 1)` 分別擷取「序號」跟「實數」。
```cpp=
#include <bits/stdc++.h>
using namespace std;
int main(){
string line;
while (getline(cin, line)){
stringstream ss(line);
string s;
double n;
double sum = 0;
while (ss >> s){
int pos = s.find(':');
int number = stoi(s.substr(pos - 1));
n = stod(s.substr(pos + 1));
if (number % 2 == 1){
sum += n;
}
else{
sum -= n;
}
}
cout << sum << '\n';
}
}
```
### Uva 10082 - WERTYU
PDF Source:https://onlinejudge.org/external/100/10082.pdf
Uva Online Judge:https://onlinejudge.org/index.php?option=com_onlinejudge&Itemid=8&category=24&page=show_problem&problem=1023
Zerojudge:https://zerojudge.tw/ShowProblem?problemid=c054
題目的意思是輸入的字元都是在 QWERTY 鍵盤上往右錯了一位的結果,然後需要將它往左移回一位。
解題思路:
- 建立對照表:鍵盤上的所有字元都存進一個字串。
- 尋找、替換:讀取輸入的每一個字元,在對照表中找到該字元的位置,然後輸出它左邊那一個字元。
- 特殊字元處理:空白字元(Space)不需要轉換,直接輸出。
範例程式碼:
```cpp=
#include <bits/stdc++.h>
using namespace std;
int main(){
string keyboard = "`1234567890-=QWERTYUIOP[]\\ASDFGHJKL;'ZXCVBNM,./";
char c;
while (cin.get(c)){
int pos = keyboard.find(c);
if (pos != string::npos){
cout << keyboard[pos-1];
}
else{
cout << c;
}
}
}
```
### LeetCode 125. Valid Palindrome
Problem Source:https://leetcode.com/problems/valid-palindrome/description/
迴文判斷。
解題思路:
觀察題目,會發現要將字元改小寫,再判斷迴文才可以。
建立 a 跟 b 字串,a 作為原字串,b 是反轉後的字串,要比較兩者是否相同。
針對字母字元跟數字字元這兩個加進去 a 裡面,之後可以用 `reverse()` 來反轉字串。
範例程式碼:
```cpp=
class Solution {
public:
bool isPalindrome(string s) {
string a;
for (const char& c : s){
if (isalpha(c) || isdigit(c)){
a += tolower(c);
}
}
string b = a;
reverse(b.begin(), b.end());
return a == b;
}
};
```
### ZJ a065. 提款卡密碼
Problem Source:https://zerojudge.tw/ShowProblem?problemid=a065
懂 ASCII Code 就知道怎麼做。基本上就是字元 - 字元,會得到之間的差值,記得用 `abs()`,有可能會有負號。
範例程式碼:
```cpp=
#include <bits/stdc++.h>
using namespace std;
int main(){
string line;
getline(cin, line);
for (int i = 0; i < line.size() - 1; ++i){
cout << abs(line[i + 1] - line[i]);
}
}
```
### Uva 494 - Kindergarten Counting Game
PDF Source:https://onlinejudge.org/external/4/494.pdf
Uva Online Judge:https://onlinejudge.org/index.php?option=com_onlinejudge&Itemid=8&category=24&page=show_problem&problem=435
Zerojudge:https://zerojudge.tw/ShowProblem?problemid=a011
解題思路:運用 `isalpha()` 判斷是否為字母,並建立一個 `bool inWord = false` 判斷目前是不是一個單字,如果碰到非字母字元,則表示已經跳到下個字去了。
範例程式碼:
```cpp=
#include <bits/stdc++.h>
using namespace std;
int main(){
string line;
while (getline(cin, line)){
int count = 0;
bool inWord = false;
for (const char& c : line){
if (isalpha(c)){
if (!inWord) inWord = true;
}
else{
if (inWord){
count++;
inWord = false;
}
}
}
if (inWord) count++;
cout << count << '\n';
}
}
```
### LeetCode 151. Reverse Words in a String
Problem Source:https://leetcode.com/problems/reverse-words-in-a-string/description/
解題思路:運用 stringstream,將每個單字推到 vector 裡面,之後用 for 從 vector 最右邊開始存取,並用 result 字串變數串接單字。
範例程式碼:
```cpp=
class Solution {
public:
string reverseWords(string s) {
vector <string> lines;
stringstream ss(s);
string line, result;
while (ss >> line) lines.push_back(line);
if (lines.empty()) return "";
result = lines.back();
for (int i = lines.size() - 2; i >= 0; --i){
result += " " + lines[i];
}
return result;
}
};
```