1

以下のようなプログラムを書いています。

  • 指定されたディレクトリで正しい拡張子を持つすべてのファイルを検索します
  • Foreach、それらのファイルで特定の文字列のすべての出現箇所を検索します
  • 各行を印刷する

これを一連のジェネレーター関数 (yield return一度に 1 つの項目を呼び出して遅延ロードされたもののみを返すもの) として機能的に記述したいので、私のコードは次のようになります。

IEnumerable<string> allFiles = GetAllFiles();
IEnumerable<string> matchingFiles = GetMatches( "*.txt", allFiles );
IEnumerable<string> contents = GetFileContents( matchingFiles );
IEnumerable<string> matchingLines = GetMatchingLines( contents );

foreach( var lineText in matchingLines )
  Console.WriteLine( "Found: " + lineText );

これで問題ありませんが、最後にいくつかの統計を出力したいと思います。このようなもの:

Found 233 matches in 150 matching files. Scanned 3,297 total files in 5.72s

問題は、上記のように「純粋な関数」スタイルでコードを記述すると、各項目が遅延ロードされることです。
最後の foreach ループが完了するまで、合計で一致するファイルの数しかわかりません。また、一度に 1 つのアイテムしかyield編集されないため、コードには以前に見つかったものの数を追跡する場所がありません。LINQ のmatchingLines.Count()メソッドを呼び出すと、コレクションが再列挙されます。

この問題を解決する方法はたくさん考えられますが、どれもやや醜いようです。これは、人々が以前にやったことがあると思います。これを行うためのベスト プラクティスの方法を示す優れたデザイン パターンがあると確信しています。

何か案は?乾杯

4

6 に答える 6

2

プロセスを「Matcher」クラスにカプセル化する必要があると言えます。このクラスでは、メソッドが進行中に統計をキャプチャします。

public class Matcher
{
  private int totalFileCount;
  private int matchedCount;
  private DateTime start;
  private int lineCount;
  private DateTime stop;

  public IEnumerable<string> Match()
  {
     return GetMatchedFiles();
     System.Console.WriteLine(string.Format(
       "Found {0} matches in {1} matching files." + 
       " {2} total files scanned in {3}.", 
       lineCount, matchedCount, 
       totalFileCount, (stop-start).ToString());
  }

  private IEnumerable<File> GetMatchedFiles(string pattern)
  {
     foreach(File file in SomeFileRetrievalMethod())
     {
        totalFileCount++;
        if (MatchPattern(pattern,file.FileName))
        {
          matchedCount++;
          yield return file;
        }
     }
  }
}

仕事のコーディングをすることになっているので、ここでやめますが、一般的な考え方はそこにあります。「純粋な」関数型プログラムの全体的なポイントは、副作用がないことであり、このタイプの静的計算は副作用です。

于 2009-01-07T02:43:18.457 に答える
2

二つの案が思いつきます

  1. コンテキスト オブジェクトを渡し、列挙子から (文字列 + コンテキスト) を返す - 純粋に機能的なソリューション

  2. 統計 ( CallContext ) にスレッド ローカル ストレージを使用すると、高度なコンテキストのスタックをサポートできます。したがって、このようなコードになります。

    using (var stats = DirStats.Create())
    {
        IEnumerable<string> allFiles = GetAllFiles();
        IEnumerable<string> matchingFiles = GetMatches( "*.txt", allFiles );
        IEnumerable<string> contents = GetFileContents( matchingFiles );
        stats.Print()
        IEnumerable<string> matchingLines = GetMatchingLines( contents );
        stats.Print();
    } 
    
于 2009-01-07T02:49:48.833 に答える
1

私は Bevan のコードを取り、満足するまでリファクタリングしました。楽しいもの。

public class Counter
{
    public int Count { get; set; }
}

public static class CounterExtensions
{
    public static IEnumerable<T> ObserveCount<T>
      (this IEnumerable<T> source, Counter count)
    {
        foreach (T t in source)
        {
            count.Count++;
            yield return t;
        }
    }

    public static IEnumerable<T> ObserveCount<T>
      (this IEnumerable<T> source, IList<Counter> counters)
    {
        Counter c = new Counter();
        counters.Add(c);
        return source.ObserveCount(c);
    }
}


public static class CounterTest
{
    public static void Test1()
    {
        IList<Counter> counters = new List<Counter>();
  //
        IEnumerable<int> step1 =
            Enumerable.Range(0, 100).ObserveCount(counters);
  //
        IEnumerable<int> step2 =
            step1.Where(i => i % 10 == 0).ObserveCount(counters);
  //
        IEnumerable<int> step3 =
            step2.Take(3).ObserveCount(counters);
  //
        step3.ToList();
        foreach (Counter c in counters)
        {
            Console.WriteLine(c.Count);
        }
    }
}

期待通りの出力: 21, 3, 3

于 2009-01-08T02:07:51.710 に答える
0

これらの関数があなた自身のものであると仮定すると、私が考えることができる唯一のことは Visitor パターンであり、それぞれのことが起こったときにあなたを呼び出す抽象的なビジター関数を渡します。例: ILineVisitor を GetFileContents に渡します (ファイルを複数の行に分割すると想定しています)。ILineVisitor には OnVisitLine(String line) のようなメソッドがあり、それから ILineVisitor を実装して適切な統計を保持することができます。ILineMatchVisitor、IFileVisitor などで洗い流して繰り返します。または、それぞれの場合で異なるセマンティックを持つ OnVisit() メソッドで単一の IVisitor を使用することもできます。

関数はそれぞれ Visitor を取得し、適切なタイミングで OnVisit() を呼び出す必要がありますが、これは面倒に思えるかもしれませんが、少なくとも、ここで行っていること以外にも、多くの興味深いことを実行するために Visitor を使用できます。 . 実際、OnVisitLine(String line) の一致をチェックするビジターを GetFileContents に渡すことで、GetMatchingLines の書き込みを実際に回避できます。

これは、あなたがすでに考えていた醜いものの 1 つですか?

于 2009-01-07T02:41:00.443 に答える