# Python 及 C++ 由純文字檔輸入資料
> 作者:王一哲
> 日期:2023年8月8日
<br />
## 前言
在程式競賽及 APCS 檢定中,需要從標準輸入讀取測資,通常測資會是用空格分隔的純文字檔,而且經常沒有說明測資的行數,要一直讀取到檔案結尾 (end-of-file, EOF),甚至每行的資料數量也不固定。假設純文字檔 data.txt 的內容為
```python
1
2 3
4 5 6
7 8 9 10
```
以下是用 Python 及 C++ 從純文字檔讀取資料的方法。
<br />
## Python 方法1:使用 sys.stdin
例如以下的程式碼
```python=
import sys
a = [] # 儲存資料用的空白串列 a
for line in sys.stdin: # 如果 sys.stdin 有讀到資料,將資料儲存到字串 line,繼續執行 for 迴圈
a.append(list(map(int, line.split()))) # 讀取用空格分隔的資料
#a.append(list(map(int, line.split(',')))) # 讀取用逗號分隔的資料
print(a) # 印出串列 a
```
<br />
其中最難看懂的是第6行,因為 Python 經常把好幾毎個工具擠在同一行,解讀程式碼的原則為
1. 由最內層的括號向外、向左讀
2. 同層的括號內由左向右讀
第6行的功能是
1. line.split():將字串 line 使用 split() 依照空格拆解成數個字串。
2. map(int, line.split()):將拆解後的字串用 map 轉換成整數 int。
3. list(...):將轉換後的一串整數用 list 組成串列。
4. a.append(...):將轉換後的串列用 append 加到串列 a 最後面,因此 a 是二維串列。
<br />
於 Linux 的命令列介面 (command-line interface, CLI) 執行程式時,如果程式碼 read.py 與 data.txt 放在同一個資料夾中,可以使用以下的指令執行程式
```bash
python3 read.py < data.txt
```
如果是使用 Windows PowerShell,則要改用以下的指令
```bash
Get-Content data.txt | python.exe test.py
```
畫面上會印出
```python
[[1], [2, 3], [4, 5, 6], [7, 8, 9, 10]]
```
<br />
## Python 方法2:使用 try & except
例如以下的程式碼
```python=
a = [] # 儲存資料用的空白串列 a
while True: # 無窮迴圈
try: # 試著用 input() 從標準輸入讀取資料
a.append(list(map(int, input().split()))) # 讀取用空格分隔的資料
#a.append(list(map(int, input().split(',')))) # 讀取用逗號分隔的資料
except EOFError: # 如果 input() 沒有讀到資料會回傳錯誤訊息 EOFError
break # 停止 while 迴圈
print(a) # 印出串列 a
```
<br />
使用 input() 可以從標準輸入讀取資料,但是沒有讀到資料時會回傳錯誤訊息 EOFError 並停止程式,因此 except EOFError: break 的功能,就是遇到 EOFError 時會執行冒號後的程式碼 break,跳出 while 迴圈。如果程式碼 read.py 與 data.txt 放在同一個資料夾中,可以使用以下的指令執行程式
```bash
python3 read.py < data.txt
```
如果是使用 Windows PowerShell,則要改用以下的指令
```bash
Get-Content data.txt | python.exe test.py
```
畫面上會印出
```python
[[1], [2, 3], [4, 5, 6], [7, 8, 9, 10]]
```
<br />
## C++:使用 sstream
例如以下的程式碼
```cpp=
#include <iostream>
#include <string>
#include <sstream>
#include <vector>
using namespace std;
int main() {
string s; // 暫存資料用的字串 s
stringstream ss; // 暫存資料用的字串流 ss
vector<vector<int>> a; // 儲存資料用的二維 vector
// 逐行讀取資料的 while 迴圈
while(getline(cin, s)) { // 如果 getline 有從 cin 讀到資料,將資料指定給 s,繼續執行 while 迴圈
ss.clear(); // 先清除 ss 的內容,否則無法存入第2行及後面的資料
ss << s; // 將 s 的指定給 ss
vector<int> b; // 暫存資料用的一維 vector
while(ss >> s) { // 由 ss 依序將資料指定給 s,如果 ss 中有資料繼續執行 while 迴圈
b.push_back(stoi(s)); // 將 s 轉成 int,加到 b 最後面
}
a.push_back(b); // 將 b 加到 a 最後面
}
// 印出 a 的內容,資料用空格分隔,每列最後一筆資料後方不加空格、直接換行
for(auto it = a.begin(); it != a.end(); it++) {
for(auto it2 = (*it).begin(); it2 != (*it).end(); it2++) {
cout << *it2 << " \n"[it2 == (*it).end()-1];
}
}
return 0;
}
```
<br />
於命令列介面 (command-line interface, CLI) 執行程式時,如果編譯後的可執行檔 a.out 與 data.txt 放在同一個資料夾中,可以使用以下的指令執行程式
```bash
./a.out < data.txt
```
如果是使用 Windows PowerShell,編譯後的可執行檔 test.exe 與 data.txt 放在同一個資料夾中,改用以下的指令
```bash
Get-Content data.txt | .\test.exe
```
畫面上會印出
```cpp
1
2 3
4 5 6
7 8 9 10
```
<br />
## 結語
以上是3種我常用的方法,網路上還能找到有許多不同的方法,甚至 Python 的 NumPy 當中還有更方便的工具,例如 genfromtxt。不過在程式競賽及 APCS 檢定不能使用 NumPy,畢竟它太過強大。
<br />
## 延伸閱讀
〈[使用 numpy.genfromtxt 從文字檔中讀取資料](https://hackmd.io/@yizhewang/HyFxPWKXB)〉
<br />
## 參考資料
1. [sys — System-specific parameters and functions: stdin](https://docs.python.org/3/library/sys.html#sys.stdin)
2. [8. Errors and Exceptions](https://docs.python.org/3/tutorial/errors.html)
3. [Python EOFError](https://docs.python.org/3/library/exceptions.html#EOFError)
4. [cplusplus.com String streams](https://cplusplus.com/reference/sstream/)
---
###### tags:`C++`、`Python`