熱門文章

2023年8月8日 星期二

Python 及 C++ 由純文字檔輸入資料

作者:王一哲
日期: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 經常把好幾毎個工具擠在同一行,解讀程式碼的原則為
  1. 由最內層的括號向外、向左讀
  2. 同層的括號內由左向右讀
第6行的功能是
  1. line.split():將字串 line 使用 split() 依照空格拆解成數個字串。
  2. map(int, line.split()):將拆解後的字串用 map 轉換成整數 int。
  3. list(...):將轉換後的一串整數用 list 組成串列。
  4. 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 從文字檔中讀取資料


參考資料


  1. sys — System-specific parameters and functions: stdin
  2. 8. Errors and Exceptions
  3. Python EOFError
  4. cplusplus.com String streams



HackMD 版本連結:https://hackmd.io/@yizhewang/BJXD0x6oh

沒有留言:

張貼留言