2012-01-10 3 views
6

먼저이 문제의 규모에 대해 사과하겠습니다.하지만 실제로 기능적으로 생각하려고합니다. 그리고이 문제는 제가 해결해야 할 더 까다로운 문제 중 하나입니다.기능 파일 "스캐너"작성 방법

기능적인 방식으로, 특히 F #에서 발생한 문제를 어떻게 처리 할 수 ​​있는지에 대한 제안을 받고 싶습니다. 나는 디렉터리 목록을 살펴보고 정규식 패턴 목록을 사용하여 디렉터리에서 검색된 파일 목록을 필터링하고 두 번째 정규식 패턴 목록을 사용하여 수집 된 파일의 텍스트에서 일치하는 항목을 찾는 프로그램을 작성하고 있습니다. 이 파일에서 주어진 정규 표현식 패턴과 일치하는 각 텍스트 조각에 대해 파일 이름, 줄 인덱스, 열 인덱스, 패턴 및 일치 값을 반환하기를 원합니다. 또한 예외를 기록해야하고 세 가지 가능한 예외 시나리오가 있습니다. 디렉토리를 열 수 없거나 파일을 열 수 없으며 파일의 내용을 읽지 못했습니다. 마지막 요구 사항은 성냥을 위해 "스캔 된"파일의 양이 매우 클 수 있기 때문에이 모든 게으른 일일 필요가 있습니다. 나는 잘 읽으며 잘 수행하는 "좋은"솔루션에 관심이있는만큼 "순수한"기능적 솔루션에 대해서 너무 걱정하지 않습니다. 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

파섹과 같은 기능 파서를 보았습니까? –

+1

이것은 많은 텍스트입니다. 그것을 읽기 쉽게 깨뜨려보십시오. – Marcin

+0

나는 단순히 인터페이스와 Object Expression을 사용하여 인스턴스를 만들고이를 C# 코드에 표시합니다. –

답변

8

가장 좋은 방법은 가장 간단한 솔루션으로 시작한 다음 확장하는 것입니다. 현재 접근 방식은 두 가지 이유로 나에게 책을 읽어 매우 어려운 것 같다 :

  • 코드는 F 번호 너무 일반적인하지 않은 패턴의 콤비와 기능 조성물을 많이 사용합니다. 일부 처리는 시퀀스 표현식을 사용하여보다 쉽게 ​​작성 될 수 있습니다.

  • 이 코드는 모두 하나의 함수로 작성되었지만 여러 함수로 분리되어 있으면 상당히 복잡하고 읽기 쉽습니다.

은 아마 하나의 파일을 테스트하는 기능의 코드를 분할하여 시작 (예를 들어 fileMatches)와 파일을 통해 걸어 fileMatches를 호출하는 기능을한다. 주요 반복은 꽤 잘 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 } 

그런 다음 당신이 더 많은 읽을 수있는 코드를 만들 것입니다 단지 두 개의 매개 변수를 함수로 fileMatchesscanner 모두를 정의 할 수 있습니다 : 간단한 유형 또는 레코드의 매개 변수를 포장하는 것은 좋은 생각이 될 수 있습니다. 예 :

// 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 }