1. 程式人生 > >去除原始檔中的重複行的程式流程及其C程式碼實現

去除原始檔中的重複行的程式流程及其C程式碼實現

一、需求描述
要求對一個包含若干行記錄且某幾條記錄相同的檔案(原始檔)實現去重操作,並將去重之後的記錄寫入到另外一個檔案(目的檔案)中。也即最後生成的檔案中沒有內容相同的兩行記錄。如果原始檔中兩條記錄之間有空行,則在目的檔案中一併將其去掉。
兩條記錄相同的標準是:
1) 字元個數及內容完全相同。
2) 去除空格及回車換行符之後的內容完全相同。
示例:
原始檔樣例:

ABCD
EFGH
abcd
AB CD
abcd

12345

對應的目的檔案樣例:

ABCD
EFGH
abcd
12345

二、程式總體流程
為了實現去重操作,我們考慮使用連結串列資料結構。先將原始檔中的記錄內容逐條讀取,與連結串列中已經存在的記錄內容相比較,如果沒有與之相同的,則將該條記錄加入連結串列。當原始檔中的所有記錄內容都讀取完成之後,再將連結串列中的記錄內容寫入到目的檔案中。
程式的總體流程如圖1所示。
這裡寫圖片描述


圖1 程式的總體流程

三、重要程式流程及資料結構介紹
1.存放記錄內容的連結串列
本程式使用連結串列來存放從原始檔中讀取到的每條記錄,該連結串列的結構如下:

typedef struct T_FileInfoStruct
{
    UINT8  szContentLine[256];
    struct T_FileInfoStruct  *pNext;
} T_FileInfo;

2.判斷某條記錄是否已存在於連結串列的函式
每當讀取到一條記錄,就要判斷該條記錄是否已經存在於連結串列中了(第一條記錄除外)。判斷操作非常的簡單,只需要遍歷整個連結串列就可以了。該操作由IsInList函式實現,其具體程式碼如下:

UINT32 IsInList(T_FileInfo *ptContentListHead, UINT8 *pszContentLine)
{
    T_FileInfo  *pTmpInfo = ptContentListHead;

    if (ptContentListHead == NULL || pszContentLine == NULL)
    {
        printf("IsInList: input parameter(s) is NULL!\n");
        return 0;
    }

    while (pTmpInfo != NULL
) { if (strncmp(pszContentLine, pTmpInfo->szContentLine, strlen(pszContentLine)) == 0) // 存在於連結串列中 { return 1; } pTmpInfo = pTmpInfo->pNext; } if (pTmpInfo == NULL) // 不存在於連結串列中 { return 0; } }

3.去除記錄的回車換行符、去除記錄中的空格的函式
RmNewLine函式用於去除記錄的回車換行符,RemoveSpaceInStr函式用於去除記錄中的空格。這兩個函式的程式程式碼請見附錄。

四、程式編譯及執行結果
將RemoveRepeatLine.c(詳細的程式碼請見附錄)程式上傳到Linux機器上,使用gcc -g -o RemoveRepeatLine RemoveRepeatLine.c命令對其編譯,生成RemoveRepeatLine。
在本程式中,我們使用的原始檔為TestFile.txt,位於當前使用者的zhouzhaoxiong/zzx/RemoveRepeatLine/TestFile目錄下;生成的目的檔案為ResultFile.txt,與原始檔位於相同的目錄之下。
本人使用的原始檔TestFile.txt的內容為:

ABCD
EFGH
AB CD
1243565
dfbfdj
dgbsjn

1234 n
dsaghhn
 zvb
 vc

awsswgh
EFGH


zvb

1234 n
fedhh
 EFG H



執行RemoveRepeatLine命令之後,生成結果檔案ResultFile.txt,其內容為:

ABCD
EFGH
1243565
dfbfdj
dgbsjn
1234n
dsaghhn
zvb
vc
awsswgh
fedhh

可見,程式滿足了需求,去除了重複的行,同時將多餘的空行也去掉了。

五、總結
關於本程式,有如下幾點說明:
第一,連結串列這種資料結構在實際的軟體開發專案中應用得非常的廣泛,大家一定要熟練掌握其用法,特別是如何向連結串列中插入資料、如何遍歷整個連結串列、如何清空連結串列等。
第二,寫檔案函式WriteToFile中,在“strncpy(szContentBuf, ptContentListHead->szContentLine, strlen(ptContentListHead->szContentLine));”語句之前,一定要加上“memset(szContentBuf, 0x00, sizeof(szContentBuf));”語句,否則寫入檔案中的內容可能不是我們想要的。大家也可以在自己的機器上試試,看加與不加“memset(szContentBuf, 0x00, sizeof(szContentBuf));”語句,生成的結果檔案有何區別。
第三,在有關話單生成及處理的程式中,對源話單檔案進行去重操作是大家必須考慮的問題。本文中的程式為相關專案的開發人員提供了參考。

附:完整的程式程式碼

/**********************************************************************
* 版權所有 (C)2015, Zhou Zhaoxiong。
*
* 檔名稱:RemoveRepeatLine.c
* 檔案標識:無
* 內容摘要:去除原始檔中的重複行
* 其它說明:無
* 當前版本:V1.0
* 作    者:Zhou Zhaoxiong
* 完成日期:20151209
*
**********************************************************************/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

// 資料型別重定義
typedef unsigned char   UINT8;
typedef signed   int    INT32;
typedef unsigned int    UINT32;

// 存放檔案每行內容的連結串列
typedef struct T_FileInfoStruct
{
    UINT8  szContentLine[256];
    struct T_FileInfoStruct  *pNext;
} T_FileInfo;


// 函式宣告
void RemRepLineAndWriResFile(UINT8 *pszTestFile);
void RmNewLine(UINT8 *pInStr);
UINT32 IsInList(T_FileInfo *ptContentListHead, UINT8 *pszContentLine);
void WriteToFile(T_FileInfo *ptContentListHead);
void ClearList(T_FileInfo *ptContentListHead);
void RemoveSpaceInStr(UINT8 *pszStr, UINT32 iStrLen);


/**********************************************************************
* 功能描述:主函式
* 輸入引數:無
* 輸出引數:無
* 返 回 值:無
* 其它說明:無
* 修改日期       版本號        修改人            修改內容
* ---------------------------------------------------------------
* 20151209        V1.0     Zhou Zhaoxiong          建立
***********************************************************************/
INT32 main()
{   
    UINT8  szTestFile[128] = {0};

    // 拼裝配置檔案路徑
    snprintf(szTestFile, sizeof(szTestFile)-1, "%s/zhouzhaoxiong/zzx/RemoveRepeatLine/TestFile/TestFile.txt", getenv("HOME"));

    RemRepLineAndWriResFile(szTestFile);   // 呼叫函式完成去重及寫檔案的操作

    return 0;              
}


/**********************************************************************
* 功能描述:去除原始檔中的重複行, 並將去重之後的內容寫入結果檔案中
* 輸入引數:pszTestFile-帶路徑的測試檔名
* 輸出引數:無
* 返 回 值:無
* 其它說明:無
* 修改日期       版本號        修改人            修改內容
* ---------------------------------------------------------------
* 20151209        V1.0     Zhou Zhaoxiong          建立
***********************************************************************/
void RemRepLineAndWriResFile(UINT8 *pszTestFile)
{
    UINT8      szContentLine[256] = {0};
    UINT32     iLineLen           = 0;
    UINT32     iRetVal            = 0;

    FILE       *fp                = NULL; 
    T_FileInfo *ptContentListHead = NULL;
    T_FileInfo *ptContentListTail = NULL;
    T_FileInfo *ptCurrentContent  = NULL; 

    if (pszTestFile == NULL)
    {
        printf("RemRepLineAndWriResFile: pszTestFile is NULL!\n");
        return;
    }

    printf("RemRepLineAndWriResFile: now, begin to process file %s\n", pszTestFile);

    if ((fp = fopen(pszTestFile, "r")) == NULL) 
    {
        printf("RemRepLineAndWriResFile: open file %s failed!\n", pszTestFile);
        return;
    }
    else
    {
        ptContentListHead = NULL;
        ptContentListTail = NULL; 

        while (feof(fp) == 0 && ferror(fp) == 0)
        {
            memset(szContentLine, 0x00, sizeof(szContentLine));
            if (fgets(szContentLine, sizeof(szContentLine), fp) == NULL)    // 從原始檔中讀取一行內容
            {
                printf("RemRepLineAndWriResFile: get line null, break.\n");
                break;
            }
            else
            {
                printf("RemRepLineAndWriResFile: get content line: %s\n", szContentLine);
            }

            RmNewLine(szContentLine);      // 去掉字串後面的回車換行符
            RemoveSpaceInStr(szContentLine, strlen(szContentLine));  // 去掉字串中的空格

            iLineLen = strlen(szContentLine); 
            if (iLineLen == 0)        // 如果該行的有效長度為0, 則繼續讀取下一行
            {
                printf("RemRepLineAndWriResFile: the length of line is 0, continue to read the next content line.\n");
                continue;
            }

            if (ptContentListHead != NULL)    // 判斷當前行是否已經存在了
            {
                iRetVal = IsInList(ptContentListHead, szContentLine);
                if (iRetVal == 1)   // 已經存在
                {
                    printf("RemRepLineAndWriResFile: this content line has already existed.\n");
                    continue;
                }
            }

            // 將當前行加入連結串列中
            ptCurrentContent = (T_FileInfo *)malloc(sizeof(T_FileInfo));
            if (ptCurrentContent == NULL)
            {
                printf("RemRepLineAndWriResFile: exec malloc failed, memory may be not enough.\n");
                return;
            }

            memcpy(ptCurrentContent->szContentLine, szContentLine, strlen(szContentLine));

            if (ptContentListHead == NULL)   // 連結串列為空時作為連結串列頭
            { 
                ptContentListHead = ptCurrentContent;
                ptContentListTail = ptCurrentContent;
            }
            else
            {
                if (ptContentListTail != NULL)  // 插入連結串列尾部
                {

                    ptContentListTail->pNext = ptCurrentContent;
                    ptContentListTail        = ptCurrentContent;
                }
            }
        }
        // 原始檔使用完畢
        fclose(fp);
        fp = NULL;
    }

    // 將去重之後的結果寫入檔案中
    WriteToFile(ptContentListHead);

    // 清空連結串列
    ClearList(ptContentListHead);   
    ptContentListHead = NULL;
}


/**********************************************************************
* 功能描述:去掉字串後面的回車換行符
* 輸入引數:pInStr-輸入字串
* 輸出引數:無
* 返 回 值:無
* 其它說明:無
* 修改日期       版本號        修改人            修改內容
* ---------------------------------------------------------------
* 20151209        V1.0     Zhou Zhaoxiong          建立
***********************************************************************/
void RmNewLine(UINT8 *pInStr)
{
    UINT32  iStrLen = 0;

    if (pInStr == NULL)
    {
        printf("RmNewLine: pInStr is NULL!\n");
        return;
    }

    iStrLen = strlen(pInStr);
    while (iStrLen > 0)
    {
        if (pInStr[iStrLen-1] == '\n' || pInStr[iStrLen-1] == '\r')
        {
            pInStr[iStrLen-1] = '\0';
        }
        else
        {
            break;
        }

        iStrLen --;
    }
}


/**********************************************************************
* 功能描述:判斷某行內容是否已經存在於連結串列中了
* 輸入引數:pInStr-輸入字串
* 輸出引數:無
* 返 回 值:1-存在  0-不存在
* 其它說明:無
* 修改日期       版本號        修改人            修改內容
* ---------------------------------------------------------------
* 20151209        V1.0     Zhou Zhaoxiong          建立
***********************************************************************/
UINT32 IsInList(T_FileInfo *ptContentListHead, UINT8 *pszContentLine)
{
    T_FileInfo  *pTmpInfo = ptContentListHead;

    if (ptContentListHead == NULL || pszContentLine == NULL)
    {
        printf("IsInList: input parameter(s) is NULL!\n");
        return 0;
    }

    while (pTmpInfo != NULL)
    {
        if (strncmp(pszContentLine, pTmpInfo->szContentLine, strlen(pszContentLine)) == 0)   // 存在於連結串列中
        {
            return 1;
        }
        pTmpInfo = pTmpInfo->pNext;   
    }

    if (pTmpInfo == NULL)    // 不存在於連結串列中
    {
        return 0;
    }
}


/**********************************************************************
 * 功能描述: 把內容寫到檔案中
 * 輸入引數: ptContentListHead-檔案記錄連結串列
 * 輸出引數: 無
 * 返 回 值: 無
 * 其它說明: 無
 * 修改日期            版本號            修改人           修改內容
 * ----------------------------------------------------------------------
 * 20151209             V1.0          Zhou Zhaoxiong        建立
 ************************************************************************/
void WriteToFile(T_FileInfo *ptContentListHead)
{
    FILE   *fp                  = NULL;
    UINT8   szLocalFile[500]    = {0};
    UINT8   szContentBuf[256]   = {0};

    if (ptContentListHead == NULL)
    {
        printf("WriteToFile: input parameter is NULL!\n");
        return;
    }

    snprintf(szLocalFile, sizeof(szLocalFile)-1, "%s/zhouzhaoxiong/zzx/RemoveRepeatLine/TestFile/ResultFile.txt", getenv("HOME"));
    fp = fopen(szLocalFile, "a+");
    if (fp == NULL)
    {
         printf("WriteToFile: open local file failed, file=%s\n", szLocalFile);
         return;
    }

    while (ptContentListHead != NULL)  
    {
        memset(szContentBuf, 0x00, sizeof(szContentBuf));
        strncpy(szContentBuf, ptContentListHead->szContentLine, strlen(ptContentListHead->szContentLine));
        printf("WriteToFile: LocalFile=%s, ContentBuf=%s\n", szLocalFile, szContentBuf);

        fputs(szContentBuf, fp);
        fputs("\r\n", fp);     // 加上回車換行符
        fflush(fp);

        ptContentListHead = ptContentListHead->pNext;   
    }

    fclose(fp);
    fp = NULL;

    return;
}


/**********************************************************************
* 功能描述: 清除連結串列
* 輸入引數: ptContentListHead-連結串列指標
* 輸出引數: 無
* 返 回 值: 無
* 其它說明: 無
* 修改日期        版本號        修改人          修改內容
* ------------------------------------------------------
* 20151209        V1.0     Zhou Zhaoxiong        建立
***********************************************************************/
void ClearList(T_FileInfo *ptContentListHead)
{
    T_FileInfo *ptContentList = NULL;
    T_FileInfo *pTmpData      = NULL;

    if (ptContentListHead == NULL)
    {
        printf("ClearList: input parameter is NULL!\n");
        return;
    }

    ptContentList = ptContentListHead;
    while (ptContentList != NULL)
    {
        pTmpData = ptContentList;
        ptContentList  = ptContentList->pNext;
        free(pTmpData);
    }
}


/**********************************************************************
* 功能描述: 清除字串中的空格
* 輸入引數: pszStr-輸入的字串
             iStrLen-最大長度
* 輸出引數: 無
* 返 回 值: 無
* 其它說明: 無
* 修改日期        版本號     修改人            修改內容
* ------------------------------------------------------
* 20151209        V1.0     Zhou Zhaoxiong        建立
***********************************************************************/
void RemoveSpaceInStr(UINT8 *pszStr, UINT32 iStrLen)
{
    UINT8  szResult[256] = {0};
    UINT8  szBuf[256]    = {0};      
    UINT32 iLoopFlagSrc  = 0;
    UINT32 iLoopFlagDst  = 0;       

    if (pszStr == NULL)
    {
        return;
    }

    memcpy(szBuf, pszStr, iStrLen);

    for (iLoopFlagSrc = 0; iLoopFlagSrc < strlen(szBuf); iLoopFlagSrc ++)
    {
        if (szBuf[iLoopFlagSrc] != ' ')
        {
            szResult[iLoopFlagDst] = szBuf[iLoopFlagSrc];
            iLoopFlagDst ++;
        }
    }

    szResult[iLoopFlagDst+1] = 0;

    memcpy(pszStr, szResult, iStrLen);

    return;
}