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)
但我似乎仍然失去了我需要的信息.
也许我只是从错误的角度接近这个?从性能的角度来看,循环应该以与原始示例大致相同的顺序执行.
有关如何在更紧凑的功能表示中执行此操作的任何想法?
怎么样:
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)