2012-01-10 4 views
6

まずこの問題の規模についてお詫びしますが、実際に機能的に考えることを試みています。これは私が処理しなければならなかったより難しい問題の1つです。ファンクションファイル "scanner"の作成方法

私は機能的な方法、特にF#で問題を処理する方法についていくつかの提案を得たいと思っていました。私は、ディレクトリのリストを調べ、正規表現パターンのリストを使ってディレクトリから検索されたファイルのリストをフィルタリングし、正規表現パターンの第2のリストを使用して、捕捉されたファイルのテキストに一致するものを見つけるプログラムを書いています。私は、ファイル名、行インデックス、列インデックス、パターン、与えられた正規表現パターンに一致する各テキストの一致した値を返すようにします。また、例外を記録する必要があり、3つの例外が考えられます。ディレクトリを開くことができず、ファイルを開くことができず、ファイルから内容を読み取ることができませんでした。これの最終的な要件は、マッチのために「スキャンされた」ファイルの量が非常に大きくなる可能性があるため、この全体が怠惰である必要があります。私は「純粋な」機能的な解決策について心配しているわけではなく、よく読んでうまく機能する「良い」ソリューションに興味があります。最終的な課題の1つは、Winformツールを使用してこのアルゴリズムをUIに追加したいので、C#と相互運用することです。ここに私の最初の試みであり、うまくいけば、これは問題を明確にします:任意の入力のための

open System.Text.RegularExpressions 
open System.IO 

type Reader<'t, 'a> = 't -> 'a //=M['a], result varies 

let returnM x _ = x 

let map f m = fun t -> t |> m |> f 

let apply f m = fun t -> t |> m |> (t |> f) 

let bind f m = fun t -> t |> (t |> m |> f) 

let Scanner dirs = 
    returnM dirs 
    |> apply (fun dirExHandler -> 
     Seq.collect (fun directory -> 
      try 
       Directory.GetFiles(directory, "*", SearchOption.AllDirectories) 
      with | e -> 
       dirExHandler e directory 
       Array.empty)) 
    |> map (fun filenames -> 
     returnM filenames 
     |> apply (fun (filenamepatterns, lineExHandler, fileExHandler) -> 
      Seq.filter (fun filename -> 
       filenamepatterns |> Seq.exists (fun pattern -> 
        let regex = new Regex(pattern) 
        regex.IsMatch(filename))) 
      >> Seq.map (fun filename -> 
        let fileinfo = new FileInfo(filename) 
        try 
         use reader = fileinfo.OpenText() 
         Seq.unfold (fun ((reader : StreamReader), index) -> 
          if not reader.EndOfStream then 
           try 
            let line = reader.ReadLine() 
            Some((line, index), (reader, index + 1)) 
           with | e -> 
            lineExHandler e filename index 
            None 
          else 
           None) (reader, 0)   
         |> (fun lines -> (filename, lines)) 
        with | e -> 
         fileExHandler e filename 
         (filename, Seq.empty)) 
      >> (fun files -> 
       returnM files 
       |> apply (fun contentpatterns -> 
        Seq.collect (fun file -> 
         let filename, lines = file 
         lines |> 
          Seq.collect (fun line -> 
           let content, index = line 
           contentpatterns 
           |> Seq.collect (fun pattern ->  
            let regex = new Regex(pattern) 
            regex.Matches(content) 
            |> (Seq.cast<Match> 
            >> Seq.map (fun contentmatch -> 
             (filename, 
              index, 
              contentmatch.Index, 
              pattern, 
              contentmatch.Value)))))))))) 

感謝。更新

- ここでは、私が受け取ったフィードバックに基づいてすべての更新のソリューションです:

open System.Text.RegularExpressions 
open System.IO 

type ScannerConfiguration = { 
    FileNamePatterns : seq<string> 
    ContentPatterns : seq<string> 
    FileExceptionHandler : exn -> string -> unit 
    LineExceptionHandler : exn -> string -> int -> unit 
    DirectoryExceptionHandler : exn -> string -> unit } 

let scanner specifiedDirectories (configuration : ScannerConfiguration) = seq { 
    let ToCachedRegexList = Seq.map (fun pattern -> new Regex(pattern)) >> Seq.cache 

    let contentRegexes = configuration.ContentPatterns |> ToCachedRegexList 

    let filenameRegexes = configuration.FileNamePatterns |> ToCachedRegexList 

    let getLines exHandler reader = 
     Seq.unfold (fun ((reader : StreamReader), index) -> 
      if not reader.EndOfStream then 
       try 
        let line = reader.ReadLine() 
        Some((line, index), (reader, index + 1)) 
       with | e -> exHandler e index; None 
      else 
       None) (reader, 0) 

    for specifiedDirectory in specifiedDirectories do 
     let files = 
      try Directory.GetFiles(specifiedDirectory, "*", SearchOption.AllDirectories) 
      with e -> configuration.DirectoryExceptionHandler e specifiedDirectory; [||] 
     for file in files do 
      if filenameRegexes |> Seq.exists (fun (regex : Regex) -> regex.IsMatch(file)) then 
       let lines = 
        let fileinfo = new FileInfo(file) 
        try 
         use reader = fileinfo.OpenText() 
         reader |> getLines (fun e index -> configuration.LineExceptionHandler e file index) 
        with | e -> configuration.FileExceptionHandler e file; Seq.empty 
       for line in lines do 
        let content, index = line 
        for contentregex in contentRegexes do 
         for mmatch in content |> contentregex.Matches do 
          yield (file, index, mmatch.Index, contentregex.ToString(), mmatch.Value) } 

ここでも、任意の入力は大歓迎です。

+2

Parsecのような機能パーサーはありますか? –

+1

これはたくさんのテキストです。それを読みやすくするために分割してみてください。 – Marcin

+0

私は単にインターフェイスとオブジェクト式を使用してインスタンスを作成し、それをC#コードに公開します。 –

答えて

8

私は最善のアプローチは、最も簡単な解決策から始めて、それを拡張することだと思います。あなたの現在のアプローチは、2つの理由のために私に読み取ることが非常に困難であると考えられる:

  • コードはF#であまり一般的ではないパターンでコンビネータと機能組成物の多くを使用しています。処理の一部は、シーケンス式を使用してより簡単に記述することができます。

  • コードはすべて1つの関数として書かれていますが、それはかなり複雑で、複数の関数に分かれていると読みやすくなります。

私はおそらく、単一のファイル(​​を言う)とのファイルの上に歩くと​​を呼び出す関数をテスト関数内のコードを分割することにより開始します。メイン反復は非常にうまくF#シーケンスの表現を使って書くことができます:あなたは周りの多くのパラメータを渡す必要があるため

// Checks whether a file name matches a filename pattern 
// and a content matches a content pattern 
let fileMatches fileNamePatterns contentPatterns 
       (fileExHandler, lineExHandler) file = 
    // TODO: This can be imlemented using 
    // File.ReadLines which returns a sequence 


// Iterates over all the files and calls 'fileMatches' 
let scanner specifiedDirectories fileNamePatterns contentPatterns 
      (dirExHandler, fileExHandler, lineExHandler) = seq { 
    // Iterate over all the specified directories 
    for specifiedDir in specifiedDirectories do 
    // Find all files in the directories (and handle exceptions)  
    let files = 
     try Directory.GetFiles(specifiedDir, "*", SearchOption.AllDirectories) 
     with e -> dirExHandler e specifiedDir; [||] 
    // Iterate over all files and report those that match 
    for file in files do 
     if fileMatches fileNamePatterns contentPatterns 
        (fileExHandler, lineExHandler) file then 
     // Matches! Return this file as part of the result. 
     yield file } 

機能は、まだ非常に複雑です。単純型またはレコード内のパラメータをラップすると良いアイデアかもしれない:

type ScannerArguments = 
    { FileNamePatterns:string 
    ContentPatterns:string 
    FileExceptionHandler:exn -> string -> unit 
    LineExceptionHandler:exn -> string -> unit 
    DirectoryExceptionHandler:exn -> string -> unit } 

次に、あなたのコードがたくさん読みやすくなる、ちょうど2つのパラメータを取る関数として​​とscannerの両方を定義することができます。次のようなもの:

// Iterates over all the files and calls 'fileMatches' 
let scanner specifiedDirectories (args:ScannerArguments) = seq { 
    for specifiedDir in specifiedDirectories do 
    let files = 
     try Directory.GetFiles(specifiedDir, "*", SearchOption.AllDirectories) 
     with e -> args.DirectoryEceptionHandler e specifiedDir; [||] 
    for file in files do 
     // No need to propagate all arguments explicitly to other functions 
     if fileMatches args file then yield file } 
関連する問題