如何使用LINQ使C#'grep'更具功能性?

Jon*_*hem 9 c# linq functional-programming c#-3.0

我有一个方法,使用可枚举的"搜索字符串"跨文件执行简单的'grep'.(实际上,我正在做一个非常天真的"查找所有参考文献")

IEnumerable<string> searchStrings = GetSearchStrings();
IEnumerable<string> filesToLookIn = GetFiles();
MultiMap<string, string> references = new MultiMap<string, string>();

foreach( string fileName in filesToLookIn )
{
    foreach( string line in File.ReadAllLines( fileName ) )
    {
        foreach( string searchString in searchStrings )
        {
            if( line.Contains( searchString ) )
            {
                references.AddIfNew( searchString, fileName );
            }
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

注意:MultiMap<TKey,TValue>大致相同Dictionary<TKey,List<TValue>>,只是避免您通常遇到的NullReferenceExceptions.


我一直试图将它变成一种更"功能"的风格,使用链式LINQ扩展方法,但还没有想到它.

一次死胡同尝试:

// I get lost on how to do a loop within a loop here...
// plus, I lose track of the file name
var lines = filesToLookIn.Select( f => File.ReadAllLines( f ) ).Where( // ???
Run Code Online (Sandbox Code Playgroud)

另一个(希望这次保留文件名):

var filesWithLines =
    filesToLookIn
        .Select(f => new { FileName = f, Lines = File.ReadAllLines(f) });

var matchingSearchStrings =
    searchStrings
        .Where(ss => filesWithLines.Any(
                         fwl => fwl.Lines.Any(l => l.Contains(ss))));
Run Code Online (Sandbox Code Playgroud)

但我似乎仍然失去了我需要的信息.

也许我只是从错误的角度接近这个?从性能的角度来看,循环应该以与原始示例大致相同的顺序执行.

有关如何在更紧凑的功能表示中执行此操作的任何想法?

Bry*_*tts 9

怎么样:

var matches =
    from fileName in filesToLookIn
    from line in File.ReadAllLines(fileName)
    from searchString in searchStrings
    where line.Contains(searchString)
    select new
    {
        FileName = fileName,
        SearchString = searchString
    };

    foreach(var match in matches)
    {
        references.AddIfNew(match.SearchString, match.FileName);
    }
Run Code Online (Sandbox Code Playgroud)

编辑:

从概念上讲,查询将每个文件名转换为一组行,然后将该组行交叉连接到搜索字符串集(意味着每行与每个搜索字符串配对).该集合被过滤为匹配行,并选择每行的相关信息.

multiple from子句类似于嵌套foreach语句.每个都指示前一个范围内的新迭代.多个from子句转换为SelectMany方法,该方法从每个元素中选择一个序列并将得到的序列展平为一个序列.

所有C#的查询语法都转换为扩展方法.但是,编译器确实采用了一些技巧.一种是使用匿名类型.每当2个范围变量在同一范围内时,它们可能是幕后匿名类型的一部分.这允许任意数量的范围数据流过像Select和那样Where具有固定数量参数的扩展方法.有关详细信息,请参阅此帖子.

以下是上述查询的扩展方法转换:

var matches = filesToLookIn
    .SelectMany(
        fileName => File.ReadAllLines(fileName),
        (fileName, line) => new { fileName, line })
    .SelectMany(
        anon1 => searchStrings,
        (anon1, searchString) => new { anon1, searchString })
    .Where(anon2 => anon2.anon1.line.Contains(anon2.searchString))
    .Select(anon2 => new
    {
        FileName = anon2.anon1.fileName,
        SearchString = anon2.searchString
    });
Run Code Online (Sandbox Code Playgroud)