日期:2023年8月8日
前言
在程式競賽及 APCS 檢定中,需要從標準輸入讀取測資,通常測資會是用空格分隔的純文字檔,而且經常沒有說明測資的行數,要一直讀取到檔案結尾 (end-of-file, EOF),甚至每行的資料數量也不固定。假設純文字檔 data.txt 的內容為
1
2 3
4 5 6
7 8 9 10
以下是用 Python 及 C++ 從純文字檔讀取資料的方法。
Python 方法1:使用 sys.stdin
例如以下的程式碼
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
其中最難看懂的是第6行,因為 Python 經常把好幾毎個工具擠在同一行,解讀程式碼的原則為
- 由最內層的括號向外、向左讀
- 同層的括號內由左向右讀
- line.split():將字串 line 使用 split() 依照空格拆解成數個字串。
- map(int, line.split()):將拆解後的字串用 map 轉換成整數 int。
- list(...):將轉換後的一串整數用 list 組成串列。
- a.append(...):將轉換後的串列用 append 加到串列 a 最後面,因此 a 是二維串列。
於命令列介面 (command-line interface, CLI) 執行程式時,如果程式碼 read.py 與 data.txt 放在同一個資料夾中,可以使用以下的指令執行程式
python3 read.py < data.txt
如果是使用 Windows PowerShell,則要改用以下的指令
Get-Content data.txt | python.exe test.py
畫面上會印出
[[1], [2, 3], [4, 5, 6], [7, 8, 9, 10]]
Python 方法2:使用 try & except
例如以下的程式碼
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
使用 input() 可以從標準輸入讀取資料,但是沒有讀到資料時會回傳錯誤訊息 EOFError 並停止程式,因此 except EOFError: break 的功能,就是遇到 EOFError 時會執行冒號後的程式碼 break,跳出 while 迴圈。如果程式碼 read.py 與 data.txt 放在同一個資料夾中,可以使用以下的指令執行程式
python3 read.py < data.txt
如果是使用 Windows PowerShell,則要改用以下的指令
Get-Content data.txt | python.exe test.py
畫面上會印出
[[1], [2, 3], [4, 5, 6], [7, 8, 9, 10]]
C++:使用 sstream
例如以下的程式碼
#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;
}
於命令列介面 (command-line interface, CLI) 執行程式時,如果編譯後的可執行檔 a.out 與 data.txt 放在同一個資料夾中,可以使用以下的指令執行程式
./a.out < data.txt
如果是使用 Windows PowerShell,編譯後的可執行檔 test.exe 與 data.txt 放在同一個資料夾中,改用以下的指令
Get-Content data.txt | .\test.exe
畫面上會印出
1
2 3
4 5 6
7 8 9 10
結語
以上是3種我常用的方法,網路上還能找到有許多不同的方法,甚至 Python 的 NumPy 當中還有更方便的工具,例如 genfromtxt。不過在程式競賽及 APCS 檢定不能使用 NumPy,畢竟它太過強大。
延伸閱讀
〈使用 numpy.genfromtxt 從文字檔中讀取資料〉
參考資料
- sys — System-specific parameters and functions: stdin
- 8. Errors and Exceptions
- Python EOFError
- cplusplus.com String streams
HackMD 版本連結:https://hackmd.io/@yizhewang/BJXD0x6oh
沒有留言:
張貼留言