1

入力が文字のストリームである必要があり、プログラムが空白以外の文字と単語をカウントするプログラムを実行しました。word は、空白文字で区切られた文字のストリームとして定義されます。ここにプログラムがあります..

#include <stdio.h> 
#include<ctype.h>
#include <stdbool.h>
#include<iso646.h>

int main(void)  
{   unsigned long int wordcount = 0,charcount = 0, count=1;
    int ch;
    bool flag, prev;



    while ((ch = getchar()) != EOF) 
      {   if(isgraph(ch))
              flag=true;
          else
              flag=false;

          if(flag)
             charcount++;

          if(count ==1)
             prev = flag;

        if(count != 1)
           {   if(prev and (not flag))
                  wordcount++;
               prev = flag;
           }

          count++;
      }

    if((ch == EOF) and flag)
          wordcount++;

   printf("\nnumber of words counted are %lu \n", wordcount);
   printf("\nnumber of characters counted are %lu \n", charcount);



   return 0;

}

今、私はこのプログラムを簡単な文章でチェックしました。しかし、練習のために、これについて詳細なソフトウェア テストを行いたいと思います。どうすればそれができますか?文の数を増やすだけですか?プロジェクト・グーテンベルクで見つけたいくつかの小説からいくつかの段落を挙げようとしました. ここで他に何ができますか?また、このプログラムの効率を改善できますか?

4

2 に答える 2

3

実行するさまざまな基本的なテストがあります。

  1. 空のファイル
  2. 空白が1つだけのファイル
  3. 空白以外のファイルが1つだけ
  4. 1つの空白と1つの非空白のファイル
  5. 1つの非空白と1つの空白のファイル
  6. 複数の空白のみを含むファイル
  7. 複数の非ブランクのみを含むファイル
  8. 複数の空白とそれに続く非空白のファイル

そしてそれは続きます...これは境界テストです。コードが境界条件で正しく機能することを確認します。

getchar()から(現在は質問で修正されています)への値の割り当てunsigned long intは異常です。戻り値は、通常の文字の場合は正であり、ファイルの終わりまたはエラーの場合は負(EOF)であるため、通常、符号付きプレーンに割り当てますint

ループ後のテストch == EOFは冗長です。ループから抜け出す唯一の方法は、条件が真の場合です。

<iso646.h>および(マクロ)キーワードandを使用するnotことも珍しいことです。

最も一般的には、人々はブロックの開いた中括弧と同じ行にコードを配置しません。

charcountを設定したifブロックでインクリメントできますflag = true;elseの代わりにブロックを使用できますif (count != 1)。実際、AFAICT、あなたのコード:

if(count ==1)
    prev = flag;

if(count != 1)
    {   if(prev and (not flag))
            wordcount++;
        prev = flag;
    }

次のように書くことができます:

if (count > 1 and prev and (not flag))
    wordcount++;
prev = flag;

「カウントされた文字数」の説明は厳密には正確ではありません。これは、報告しているグラフィカル(非ブランク、非コントロール)文字の数です。ただし、これはおそらく大騒ぎの尺度の非常に重要な部分です(「単語の数」は単数であり、「ある」ではなく「ある」である必要があるという観察結果とともに)。

countゼロではなく1から開始するのは少し珍しいことです。「プログラムに読み込まれた生の文字の数より1つ多い」を記録しているようですが、これは記録するのに異常な量です。通常は、これも0に初期化し、書き直したテストを次のように変更します。

if (count != 0 and prev and (not flag))

count != 0またはcount > 0;を使用できます。符号なしの値の場合、用語は同等です。)

prev適切に初期化することで(おそらく)条件を単純化できるかもしれませんfalse

于 2012-12-16T06:21:27.100 に答える
0

次のように、テストする定数を左側に配置する習慣を身につけてください。

while (EOF != (ch = getchar()))

...これにより、== の意味で誤って = を入力してしまったときに、最も後退する余裕がないときに、数え切れないほどの時間を費やす必要がなくなります。変数を定数に割り当てることはできないため、コンパイラはエラーにフラグを立てて、お尻を救います。

私の経験では、この種のコードを読むことに慣れると、 if(, while(, の本体のどこかで探すよりも、 if(, while(, ) のすぐ隣にある方がテスト対象を見つける方がはるかに速くなります。これは、ファイルやソケットなどを開き、ファイル データを保持するために malloc() を介してメモリを割り当てるなど、テストの長いリストがある場合に特に当てはまります。

PS: いくつかの検討の後、いくつかの基本的な CS 101 について言及する価値があります...

1 つ目は、ここに古典的なケースがあります。この場合、while() ループを介した最初のパスであっても、1 文字後ろを見る必要があるためです。while() ループをシードするためです。解決策は、while() ループと同じロジックを 1 回通過する単純な if() ブロックを使用して while() ループを設定することです。(参考までに、while() は終了条件付きの無限の if() のセットです)

これを行う適切な方法は次のとおりです。その見返りは、これが while() ループを通過するたびにその最初のパスであるかどうかを確認することで、付随するすべての if() テストを捨てることができることです。ここでの最初のパスは、while() ループの前にある if() テストによって処理されます。

第 2 に、変数名が有益ではないことがわかりました。それは彼らが「間違っていた」という意味ではありませんが、あなたのコードを維持しようとする人も同様に苦労する可能性があります. 私の経験では、コードの一部をよりよく理解するにつれて、変数名はますます良くなります。それを、問題を理解しているかどうか、適切な解決策があるかどうか、およびその理由を知っているかどうかのリトマス試験紙として使用してください。

第 3 に、main() 内の変数を 1 に初期化していることに気付いた場合、現在の PassKnt が 1 に設定されているように、正しいフロー制御についてのフラグが頭に浮かぶはずです。 loop/if/while の先頭ではなく、末尾にあるループ カウンター。繰り返しますが、これにより、ロジックに疑問が生じるはずです。

注:デフォルトでは、メモ帳は Unicode 形式で保存されます。メモ帳を使用してこのプログラムのテスト ファイルを作成する場合は、必ずANSI形式で保存してください。

プログラムをわかりやすくするために残しましたが、ここでは IsGraphFlg は不要です。ループの最後で IsGraphFlg を WasGraphFlg に割り当てる代わりに、if-else ブロックの上半分と下半分の両方で行うことができます。これは、コンテキストが IsGraphFlag と同じ情報を提供するためです。

    while (EOF != (ch = fgetc(pFile)))  {
        if(isgraph(ch)) {
            IsGraphFlg=true;
            charcount++;
        }   else    {   // this char is whitespace, last char was part of a word
            IsGraphFlg=false;
            if(WasGraphFlg) {
                wordcount++;
            }
        }
        WasGraphFlg = IsGraphFlg;
        PassKnt++;
    }

また、PassKnt も現在は何の役にも立たず、不要になっていることに気付くかもしれません。

isgraph() が最適であることが示唆されていますが、bool 配列を作成し、isgraph() を使用して初期化すると、コードが実行されました (メモリ バッファーが不足しました。これは、この Dell XPS 8500 のファイルからの速度の約 10 倍です)。 ) 時間の 3 分の 2 弱 - 1 文字あたり 14.75 クロックではなく 9.25 クロック。これは完全にオプションの最適化ですが、重要なものです。

bool    IsGph[256];
for(i=0; i<sizeof(IsGph); i++)  {
    IsGph[i] = isgraph((unsigned char)i);
}

使用中、if(isgraph(i)) は、メイン キャラクターと単語カウント ループで if(IsGph[i]) に置き換えられます。

コード更新日: 2012 年 12 月 30 日

// Word_Counter.cpp : Defines the entry point for the console application.
//
#include "stdafx.h"
#include "stdafx.h"
#include <stdio.h>
#include <time.h>
#include <stdlib.h>
#include <memory.h>
#include <locale>

#define UCHAR unsigned char
#define dbl   double
#define LLONG __int64

#define PROCESSOR_HZ ((LLONG) 3400000000)
#pragma warning(disable : 4996)
//
//  function prototypes
FILE *OpenFiles (int *FileSz, char *FileName);
// -----------------------------------------------------------------------
FILE *OpenFiles (int *FileSz, char *FileName)   {
    FILE *pFile=NULL;
    if (NULL == (pFile = fopen ((char *)FileName, "r+t" ))) {
        printf ( "Can't open %s\n", FileName );
        return NULL;
    }   else    {
        fseek(pFile,0,SEEK_END);
        *FileSz = ftell(pFile);
        rewind(pFile);
        printf("\nFile size is %i", *FileSz);
        return pFile;
    }
 }
// -----------------------------------------------------------------------
int _tmain(int argc, char *argv[])  {
bool    IsGph[256];
UCHAR   *p, *pBuff=NULL;
int     WrdKnt=0,CharKnt=0;
int     i, j, FileSz, LoopKnt=3500;
time_t  Etime=0,start=0, Eclocks=0;
FILE    *pFile=NULL;
bool    WasGraphFlg=false;

    //  Initialize boolean array to detect printable characters
    for(i=0; i<sizeof(IsGph); i++)  {
        IsGph[i] = isgraph((unsigned char)i);
    }
    if(NULL == (pFile = OpenFiles(&FileSz, (char *)argv[1])))   {
        return 0;
    }
   // --- Process out of buffer, not stdin -------------------------------
    pBuff = (unsigned char *)calloc(FileSz,  sizeof(char));
    fread(pBuff, sizeof(char), FileSz, pFile);
    start = clock();
    for(i=LoopKnt; i; i--)  {
        p= pBuff;
        CharKnt=0;
        WrdKnt=0;
        for(j=FileSz; j; j--)   {
            if(IsGph[*p++]) {
                CharKnt++;
                WasGraphFlg = true;
            }   else    {           // this char is whitespace, and
                if(WasGraphFlg) {   // last char was part of word ?
                    WrdKnt++;
                }
                WasGraphFlg = false;
            }
        }
    }
    Etime  = clock() - start;
    Eclocks= Etime * PROCESSOR_HZ/(LLONG) CLOCKS_PER_SEC;
    printf("\nElapsed time for %10i loops was %10i milliseconds",
                    LoopKnt, Etime);
    printf("\nCPU cycles consumed per char were %2f\n", 
                    (dbl)Eclocks/(dbl)((LLONG)FileSz*(LLONG)LoopKnt));
    printf("\n%i words counted per loop", WrdKnt);
    printf("\n%i chars counted per loop\n", CharKnt);
    getchar();
    return _fcloseall();
    free(pBuff);
}

ファイル名を指定するコマンド ライン引数に問題がある場合は、Visual Studio の [プロジェクト] -> [プロパティ] -> [構成プロパティ] -> [全般] で、"Unicode" をひどく誤ったラベルの "マルチバイト" 文字セットに変更します。デバッガーで argv[1] のメモリをいつでも調べて、実際に argv[] にあるものを見つけることができます。

于 2012-12-16T08:55:42.077 に答える