我有一个文件列表:
fileA_20180103110932
fileA_20180103111001
fileB_20180103110901
fileC_20180103110932
fileC_20180103111502
每个文件名,我需要获取最新日期。所以结果集将是:
fileA_20180103111001
fileB_20180103110901
fileC_20180103111502
我如何使用lambda表达式?
在高级别上,我认为我必须按文件名分组(所以要做一个子串直到下划线),然后得到那些有一个计数>的文件名的最大日期。 2。
答案 0 :(得分:2)
这样的事情应该有效:
var files = new List<string>
{
"fileA_20180103110932",
"fileA_20180103111001",
"fileB_20180103110901",
"fileC_20180103110932",
"fileC_20180103111502"
};
var results = files
.Select(f => f.Split('_'))
.GroupBy(p => p[0], p => p[1])
.Select(g => g.Key + "_" + g.Max());
答案 1 :(得分:0)
显然,您的文件名中只有一个下划线。将下划线后面的部件定义为“文件日期”与您的问题无关。相关的是你的文件名有一个下划线,一个在下划线之前,一个在下划线之后。
此外,文件名不是文件,它只是一个有一些限制的字符串,尤其是你的编码文件名
所以你的问题就是这样:
给定一系列字符串,其中每个字符串只有一个下划线。下划线之前的部分称为MainPart,下划线之后的部分称为SortablePart(这就是您所谓的“文件日期”)。
您的要求是:
我想要一个linq语句,其中包含这个字符串序列和输入 作为输出包含输入的MainPart的字符串序列 字符串,后跟下划线,后跟all的第一个值 具有相同MainPart的字符串的SortableParts以降序排序 顺序。
重新解决你的问题你的linq语句相当容易。您需要一个函数将输入字符串拆分为MainPart和SortablePart。我将使用String.Split
执行此操作var result = fileNames
.Select(inputString => inputString.Split(new char[] {'_'}))
.Select(splitStringArray => new
{
MainPart = splitStringArray[0],
SortablePart = splitStringArray[1],
})
// now easy to group by MainPart:
.GroupBy(
item => item.MainPart, // make groups with same MainPart, will be the key
item => item.SortablePart) // the elements of the group
// for every group, sort the elements descending and take only the first element
.Select(group => new
{
MainPart = group.Key,
NewestElement = group // take all group elements
.SortByDescending(groupElement => groupElement) // sort in descending order
.First(),
})
// I know every group has at least one element, otherwise it wouldn't be a group
// now form the file name:
.Select(item => item.MainPart + '_' + item.NewestElement);
这是一个可怕的linq声明!
此外,如果您的文件名根本没有下划线,它将崩溃。很难保证文件名都被正确编码。
如果您的编码文件名是您在应用程序中广泛使用的内容,我的建议是为此创建一个类以及一些函数来转换为filename(字符串)并返回更容易。这将使您的编码文件名更容易被其他人理解,如果需要更容易更改,最重要的是:您可以确定文件名是否正确编码
class CodedFileName
{
private const char separator = '_';
public string MainPart {get; private set;}
public string TimePart {get; private set;}
}
如果您决定更改分隔符或接受多个分隔符(使用下划线的旧文件名和使用减号的新文件名),这会更容易
你还需要一个属性构造函数:
public CodedFileName(string mainPart, DateTime fileDate) {...}
也许构造函数采用文件名。如果没有编码,则为例外:
public CodedFileName(string fileName) {..}
public CodedFileName(FileInfo fileInfo) {...}
public bool IsProperlyCoded(string fileName) {...}
当然是ToString():
public override ToString()
{
return this.MainPart + separator + this.TimePart;
}
TODO:如果需要,可以考虑定义相等,IEquatable,IComparable,ICloneable等。
完成此操作后,优点是您可以确定您的文件名将始终正确编码。更容易理解他人,更容易改变,从而维护,最后你的linq查询将更容易(理解,维护,测试等):
作为扩展功能:see Extension methods demystified
static class CodedFileNameExtensions
{
public static CodedFileName Newest(this IEnumerable<CodedFileName> source)
{
// TODO: exception if null or source empty
return source.OrderByDescending(sourceElement => sourceElement.TimePart)
.First();
}
public static CodedFileName NewestOrDefault(this IEnumerable<CodedFileName> source)
{
// TODO: exception if null source
if (source.Any())
return source.Newest();
else
return null;
}
public static IEnumerable<CodedFileName> ExtractNewest(this IEnumerable<CodedFileName> source)
{
return groupsSameNamePart = source
.GroupBy(sourceElement => sourceElement.MainPart)
.Newest(group => group)
}
}
用法将是:
IEnumerable<string> fileNames = ...
IEnumerable<string> correctlyCodedFileNames = fileNames
.Where(fileName => fileName.IsCorrectlyCoded();
IEnumerable<CodedFileName> codedFileNames = correctlyCodedFileNames
.Select(correctlyCodedFileName => new CodedFileName(correctlyCodedFileName));
IEnumerable<CodedFileName> newestFiles = codedFileNames.ExtractNewest();
或者在一个声明中:
IEnumerable<CodedFileName> newestFiles = fileNames
.Where(fileName => fileName.IsCorrectlyCoded)
.Select(fileName => new CodedFileName(fileName)
.ExtractNewest();
现在不是那么容易理解吗?所有这一切都只需要一页编码。
因此,如果您在整个项目中使用编码文件名,我的建议是考虑为其创建一个类。