---
title: 'Project documentation template'
disqus: hackmd
---
**多媒體訊號處理-第八組**
資料自動彙整說明文件
===
# Table of Content
- **xlsx轉csv檔腳本 概念說明**
- **xlsx2csv.py demo**
- **Unique & Sort 程式自動化排序 概念說明**
- **industry_merge.c & conference_merge.c demo**
- **歸納 & 總結**
- **分工表**
---
# xlsx轉csv檔腳本概念說明
此批量將 *.xlsx轉檔為 *.csv的程式是使用Python所書寫的程式。此處需要引用了兩個函式庫來輔助並達到數據處理的目的,這兩個函式庫分別為"pandas"以及"os"。
首先,簡單介紹一下這兩個函式庫分別的功能:
- "pandas"是一個專司於資料處理和數據結構應用的函式庫,這個基於numpy的函式庫功能非常強大,可以做到excel裡的許多功能,在現世代的數據分析以及數據處理中扮演非常重要的角色。
- "os"(Operating System)是Python裡的一個標準函式庫,提供了許多與操作系統互動和文件系統操作相關的功能,例如文件路徑操作、環境變數操作、和目錄的刪除以及重新命名等等。
這邊注意到我們所使用的VScode環境中是還沒有安裝這幾個需要用到的函式庫和其擴充功能的,我們需要透過在終端機(Terminal)輸入以下兩個指令:
```python
pip install pandas
```
```python
pip install openpyxl
```
輸入且安裝完成後,我們就能在VScode的環境裡成功使用pandas函式庫裡的資料處理功能以及開啟並且讀取excel檔案了。
---
這個程式的概念為使用迴圈來遍歷source資料夾中的所有文件,並且判讀其是否為xlsx檔後再進行轉換為csv檔的腳本。為了實現此一功能,我分別使用了幾個函式庫裡的實用函數。另外,只需要輸入存放要被轉檔的xlsx資料夾之相對路徑,以及欲存放csv檔案的資料夾相對路徑便能成功執行。以下將依序做簡單的介紹:
- 使用 *os.getcwd()* 來取得當前的工作目錄。
- 使用 *os.path.join( )* 來指定輸出csv文件的完整路徑。
- 使用 *os.listdir( )* 來將指定的資料夾中遍歷。
- 使用 *pd.ExcelFile( )* 來打開Excel文件。
- 使用 *os.path.splitext( )* 來透過刪除文件擴展名並添加 '.csv'來生成轉檔後的文件名。
- 使用 *df.to_csv( )* 將DataFrame寫入指定的輸出路徑的CSV文件。
以上為將xlsx批次轉檔成csv的動作之主要概念。
接下要做的是合併所有的csv文件,並將他分組成industry & conference,以產生兩個合併的csv檔
在合併csv的動作裡,我們使用group1 和 group2 存儲不同分組(產業 & 會議)數據框的空列表。並在for迴圈中使用 os.listdir(folder_path)來讀取每一個檔案。 對於每個文件,程式檢查文件名是否以 .csv 結尾,以確保它是CSV文件。
接著使用 pd.read_csv(file_path) 讀取CSV文件,並將其存儲在一個 pandas 數據框中,並將identifier的名字修改已達到插入"貢獻組別"的標題。最後使用 "if '相關產業' in filename" 檢查文件名中是否包含特定的關鍵字以用來將其標示為其來源組別。並使用 to_csv 方法,將合併後的數據框保存到不同的CSV文件中,並印出結果。
程式碼如下所示:。
# *xlsx2csv.py* demo
程式碼如下所示:
```python=
import pandas as pd
import os
# Input the relative path of source & target folder
source_relative_path = input("請輸入source folder(存放xlsx檔案之資料夾)的相對路徑: ")
target_relative_path = input("請輸入target folder(希望存放csv檔案之資料夾)的相對路徑: ")
current_directory = os.getcwd()
source_folder_path = os.path.join(current_directory, source_relative_path)
target_folder_path = os.path.join(current_directory, target_relative_path)
if not os.path.exists(source_folder_path) or not os.path.exists(target_folder_path):
print("輸入的文件資料夾不存在,請重新輸入。")
else:
# Iterate through all files in the source folder
for file_name in os.listdir(source_folder_path):
if file_name.endswith(".xlsx"): # Process only .xlsx files
# Read the Excel file
xls = pd.ExcelFile(os.path.join(source_folder_path, file_name))
# Iterate through worksheets and convert to CSV
for sheet_name in xls.sheet_names:
df = pd.read_excel(xls, sheet_name=sheet_name)
# Generate the output CSV file name
output_name = f"{os.path.splitext(file_name)[0]}_{sheet_name}.csv"
output_path = os.path.join(target_folder_path, output_name)
# Write to the CSV file
df.to_csv(output_path, index=None, header=True)
else:
print(f"Skipping {file_name} as it is not an .xlsx file\n")
# Merge CSV files
folder_path = os.path.join(current_directory, target_relative_path)
# Lists to store dataframes for different groups
group1 = []
group2 = []
# Iterate through files in the folder
for filename in os.listdir(folder_path):
if filename.endswith('.csv'):
# Read CSV file into a pandas dataframe
file_path = os.path.join(folder_path, filename)
df = pd.read_csv(file_path)
# Extract the first three characters from the filename as an identifier
identifier = filename[:3]
df['資料貢獻組別'] = identifier
# Group dataframes based on filename or other identifiers
if '相關產業' in filename:
group1.append(df)
else:
group2.append(df)
# Concatenate dataframes for the two groups
merged_group1 = pd.concat(group1, ignore_index=True)
merged_group2 = pd.concat(group2, ignore_index=True)
# Specify different output file paths for the merged dataframes
output_file_path1 = 'merged_industry.csv'
output_file_path2 = 'merged_conference.csv'
# Save the merged dataframes to different CSV files
merged_group1.to_csv(output_file_path1, index=False)
merged_group2.to_csv(output_file_path2, index=False)
print("Converted xlsx to csv successfully!\n")
print(f"各組industry檔案已合併到: {output_file_path1}")
print(f"各組conference檔案已合併到: {output_file_path2}\n")
```
**以下兩圖分別為執行檔案前的source folder和target folder。**
Source:

Target:

---
**以下兩圖分別為執行 *xlsx2csv.py* 後的output和target folder。**
Output:

Target folder after run code:

合併後產生的兩組merged CSV檔案:

## 反思
我所完成的第一版程式中是能成功轉檔的,但是出現了一個問題那就是<font color="#f00">無法將所有的工作表都轉成csv檔,而且只能把首個工作表轉換成功而已</font>。
發覺此問題後我立即做出了修正,解決方法為再寫一個迴圈並透過讀取sheet_name來正確讀取所有的工作表,最後在轉檔後的檔名上賦予其原本工作表的名子。
---
# Unique & Sort 概念說明
程式概念說明
---
我們程式主要分為兩個conference_merge.c以及industry_merge.c,這兩個程式的內容幾乎相同差別只在於輸入程式一個是會議而另一個是產業,我們的邏輯是以處理CSV(逗號分隔值)文件為目的,並且消除重複的行,再根據它們的第二列的值對unique完成的row進行排序,然後將排序後的unique row寫入一個新的CSV文件。以下是程式碼中的主要步驟和概念:
(1)Data Structures:
程式碼使用hash table資料結構來有效地存儲和管理uniaue row。Hash table的每個bucket對應於一個unique row,並且使用linked lists在每個bucket內部處理hash collisions。
(2)Hashing:
實現了一個自定義hash函數,用於計算其內容每行的hash value。該hash value用於決定row應該存儲在hash table的哪個bucket(index)中。
(3)Eliminating Duplicates:
程式碼逐行讀取輸入的CSV文件,刪除newline字元,並將每一行(行)插入hash table中。重複的行會自動被消除,因為程式碼在插入之前會檢查hash table中是否存在該行。如果已經存在,則不會再次插入。
(4)Sorting:
在處理完輸入文件後,程式碼將所有唯一的行從hash table中收集到一個陣列中。它使用qsort函數來根據它們第二列的值對唯一行的陣列進行排序。這是使用比較函數(compare_rows)並輸入qsort來完成的。
(5)Output:
排序後的unique row(不包括重複的行)被寫入一個新的CSV文件,保留了原始輸入CSV的標頭行。
(6)Memory Management:
程式碼還負責對於row以及node的動態記憶體配置,並在不再需要時將這些內存空間釋放,以確保沒有內存洩漏。
(7)Summary:
此程式碼的主要思考邏輯是有效處理和操作CSV數據,刪除重複項並根據特定列進行排序,然後將結果存儲在一個新的CSV文件中。hash資料結構的使用幫助我們更有效率的消除重複資料,而排序步驟則確保輸出數據以指定的順序進行排序。
## 程式碼以及解釋
這個程式的主要目的是讀取一個CSV檔案,然後將其中的行進行去重和排序,最後將結果寫入到另一個CSV檔案中。我們將逐行解釋程式碼如何實現這些功能:
(一) 包含標頭文件和定義常數:
``` c=
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdbool.h>
#define TABLE_SIZE 1000
```
這裡包含了必要的標頭文件,並定義了 TABLE_SIZE 常數,用於定義哈希表的大小。
(二) 定義 Node 結構和 HashTable 結構:
``` c=
// Node structure for the linked list in the hash table
typedef struct Node {
char *row;
struct Node *next;
} Node;
// Hash table structure to store unique elements
typedef struct {
Node *table[TABLE_SIZE];
} HashTable;
```
這裡定義了兩個結構,Node 用於表示哈希表中的連結串列中的節點,HashTable 用於表示哈希表,其中包含一個固定大小的 table 數組。
(三)定義哈希函數 "hash":
``` c=
// Hash function to calculate the index
unsigned int hash(const char *key) {
unsigned int hash = 0;
while (*key) {
hash = (hash * 31) + *key;
key++;
}
return hash % TABLE_SIZE;
}
```
這個哈希函數接受一個字符串作為輸入,然後計算出一個哈希值,最後將哈希值對 TABLE_SIZE 取模,以確定在哈希表中的位置。
(四)定義清理函數 "clean_csv_row":
``` c=
// Function to remove double quotes and spaces from a CSV row
void clean_csv_row(const char *input, char *output) {
int j = 0;
for (int i = 0; input[i]; i++) {
if (input[i] != '"' && input[i] != ' ') {
output[j] = input[i];
j++;
}
}
output[j] = '\0';
}
```
這個函數用於清理CSV資料行,它接受一個輸入 input 和一個輸出 output,然後過濾 input 中的雙引號和空格,並將結果存儲在 output 中。
(五)定義比較函數 "compare_rows":
``` c=
int compare_rows(const void *a, const void *b) {
const char *row1 = *(const char **)a;
const char *row2 = *(const char **)b;
// Function to remove double quotes and spaces
char cleaned_row1[1024];
char cleaned_row2[1024];
clean_csv_row(row1, cleaned_row1);
clean_csv_row(row2, cleaned_row2);
// Compare the cleaned second columns of the rows
return strcmp(cleaned_row1, cleaned_row2);
}
```
這是用於 qsort 函數的比較函數,它接受兩個指向CSV行的指針 a 和 b,將它們轉換為 char 類型的指針,然後對它們的第二列進行比較。在比較之前,它會使用 clean_csv_row 函數來去除雙引號和空格,以防止造成比較錯誤。
(六)定義insert函數 "insert":
``` c=
// Function to insert a row into the hash table
void insert(HashTable *hashTable, const char *row) {
unsigned int index = hash(row);
// Remove double quotes and spaces from the row
char cleaned_row[1024];
clean_csv_row(row, cleaned_row);
// Check if the cleaned row already exists in the linked list
Node *current = hashTable->table[index];
while (current != NULL) {
char cleaned_current_row[1024];
clean_csv_row(current->row, cleaned_current_row);
if (strcmp(cleaned_current_row, cleaned_row) == 0) {
return; // Row already exists, so don't insert it again
}
current = current->next;
}
// Insert the new row into the linked list
Node *new_node = malloc(sizeof(Node));
if (new_node == NULL) {
fprintf(stderr, "Memory allocation error\n");
exit(EXIT_FAILURE);
}
new_node->row = strdup(row);
new_node->next = hashTable->table[index];
hashTable->table[index] = new_node;
}
```
這是一個函數,用於將一行CSV資料插入到哈希表中。它首先計算 row 的哈希值,然後檢查該行是否已經存在於哈希表的連結串列中。如果不存在,它會將該行插入連結串列。
(七)定義打印唯一排序行到CSV文件的函數 "print_sorted_unique_rows_to_csv":
``` c=
// Function to print the sorted unique rows to a CSV file
void print_sorted_unique_rows_to_csv(HashTable *hashTable, const char *inputFile, const char *outputFile) {
FILE *input = fopen(inputFile, "r");
if (input == NULL) {
perror("Error opening input file");
exit(EXIT_FAILURE);
}
FILE *output = fopen(outputFile, "w");
if (output == NULL) {
perror("Error opening output file");
exit(EXIT_FAILURE);
}
// Read and write the header row (first row) directly
char header[1024];
if (fgets(header, sizeof(header), input)) {
fputs(header, output);
}
// Create an array to store unique rows
char *unique_rows[TABLE_SIZE];
int unique_count = 0;
char line[1024];
while (fgets(line, sizeof(line), input)) {
// Remove newline character at the end of the line
line[strcspn(line, "\n")] = '\0';
// Insert the entire row into the hash table
insert(hashTable, line);
}
fclose(input);
// Iterate through the hash table and collect unique rows
for (int i = 0; i < TABLE_SIZE; i++) {
Node *current = hashTable->table[i];
while (current != NULL) {
unique_rows[unique_count] = current->row;
unique_count++;
current = current->next;
}
}
// Sort the array of unique rows based on the second column
qsort(unique_rows, unique_count, sizeof(char *), compare_rows);
// Write sorted unique rows to the CSV file
for (int i = 0; i < unique_count; i++) {
fprintf(output, "%s\n", unique_rows[i]);
}
fclose(output);
}
```
這個函數執行以下操作:
1.打開輸入和輸出文件:使用 fopen 函數分別打開輸入文件(inputFile)和輸出文件(outputFile)。如果打開輸入或輸出文件失敗,則使用 perror 函數顯示錯誤消息並退出程式。
2.讀取並寫入標頭行:使用 fgets 函數讀取輸入文件的第一行,也就是CSV文件的標頭行,並將其存儲在 header 陣列中。接著使用 fputs 函數將標頭行寫入輸出文件,以確保輸出文件保留了原始文件的標頭。
3.創建陣列以存儲唯一行:宣告了一個 char * 類型的陣列 unique_rows,這個陣列將用於存儲唯一的CSV行。創建一個整數變數 unique_count,用於跟蹤存儲的唯一行數量。
4.處理CSV文件的每一行:使用 fgets 函數遍歷輸入文件的每一行,每次讀取一行CSV資料並存儲在 line 陣列中。使用 strcspn 函數來去除 line 最後的換行符(newline character),以確保行的結尾正確。
5.插入行到哈希表:呼叫 insert 函數,將 hashTable 和 line 作為參數,以將 line 插入到哈希表中。insert 函數會確保只有唯一的行被插入,去除了重複的行。
6.關閉輸入文件:使用 fclose 函數關閉輸入文件,因為已經處理完了它。
7.收集唯一行到 unique_rows 陣列:使用迴圈遍歷哈希表中的所有節點,收集唯一行到 unique_rows 陣列中,同時增加 unique_count。
8.對 unique_rows 陣列排序:使用 qsort 函數對 unique_rows 陣列中的行進行排序,排序的方式是根據第二列的內容,使用 compare_rows 函數來比較。
9.寫入排序後的行到輸出文件:使用迴圈遍歷 unique_rows 陣列,每次寫入一行到輸出文件,然後插入換行符以分隔行。
10.關閉輸出文件:使用 fclose 函數關閉輸出文件,以確保結果被寫入文件。
(八)主函數 "main":
``` c=
int main() {
// Initialize the hash table
HashTable hashTable;
for (int i = 0; i < TABLE_SIZE; i++) {
hashTable.table[i] = NULL;
}
// Input and output file paths
const char *inputFile = "merged_conference.csv";
const char *outputFile = "output_conference.csv";
// Print the sorted unique rows (skipping header) to a new CSV file
print_sorted_unique_rows_to_csv(&hashTable, inputFile, outputFile);
// Free memory
for (int i = 0; i < TABLE_SIZE; i++) {
Node *current = hashTable.table[i];
while (current != NULL) {
Node *temp = current;
current = current->next;
free(temp->row);
free(temp);
}
}
return EXIT_SUCCESS;
}
```
主函數初始化了哈希表,然後指定輸入和輸出文件的路徑,並呼叫 "print_sorted_unique_rows_to_csv" 函數來執行去重復和排序操作。最後,它釋放了用於存儲行的內存,並退出程序。
(九)補充:定義去除括弧函數(僅用於conference_merge.c) "remove_left_parentheses",因為我們發現conference資料中有括弧會影響排序,我們想出的解法是直接刪除後再排序:
``` c=
void remove_left_parentheses(char *str) {
char *src, *dst;
for (src = dst = str; *src != '\0'; src++) {
if (*src != '(') {
*dst++ = *src;
}
}
*dst = '\0';
}
```
這個函式本質上執行一個"過濾"操作,從輸入字串中刪除所有左括號的實例,只保留不是左括號的字符。修改後的字串存儲在原始字串的相同內存位置,並以空字符結束。
- 總結
這個程式通過使用哈希表實現去重復,然後對唯一行進行排序,最終將排序後的結果寫入到輸出CSV文件中。這兩個主要部分相互協作,實現了對CSV文件的去重和排序。
# *industry_merge.c* & *conference_merge.c* demo
下圖為成功執行*industry_merge.c*後的排序結果,並儲存於新檔案output_industry.csv

---
下圖為成功執行*conference_merge.c*後的排序結果,並儲存於新檔案output_conference.csv

從以上兩圖我們可以觀察到經由執行unique & sort的程式之後,各組的CSV資料皆已經成功被排序,由英文大小寫以及字母順序排序,便達成了我們此次的目的。
# 歸納 & 總結
本次作業的所有程式demo過程提供於下:
https://youtu.be/35c751rffUc?si=rQR7L7ipnq9vsmfW
---
這次是多媒體訊號處理的第一項團隊project,也是大學生涯裡的第一次團隊程式作業,因此我們在分配分工合作上花費了不少的時間。在共同協作方面,我們善用了Visual Studio code裡的擴充軟件 -liveshare。有了這項工具的幫忙我們在共同協作上變得方便許多,另外這次也是我們第一次使用HackMD來書寫報告,不得不說真的比word方便許多,也有十分強大的共同編輯功能和簡潔的排版,期待未來能夠更活用此項工具。
雖然花費了許多時間來完成這次的程式,但實際看到他能夠運行的感覺實在是太好了!唯一的小遺憾是我們無法使用C語言去成功讀到本機directory,希望未來的我們已經有足夠的能力來解決這個問題!
# 分工表
| 工作事項/組員 | 林紘毅 | 陳品鈞 | 黃家棋 | 張庭愷 | 柯宣卉 |
| ------------------ | ------ | ------ | ------ | --- | --- |
| xlsx2csv.py 程式 | ✔ | | | | |
| xlsx2csv.py 報告 | ✔ | | | | |
| CSV_merge.py 程式 | ✔ | | | | |
| CSV_merge.py 報告 | ✔ | | | | |
| Unique & Sort 程式 | | ✔ | ✔ | | |
| Unique & Sort 報告 | | ✔ | ✔ | | |
| 歸納 & 總結demo | ✔ | | | | |
| Excel資料查詢 | ✔ | ✔ | ✔ | ✔ | ✔ |