请考虑以下三个示例列表:

List<string> localPatientsIDs = new List<string> { "1550615", "1688", "1760654", "1940629", "34277", "48083" };

List<string> remotePatientsIDs = new List<string> { "000-007", "002443", "002446", "214", "34277", "48083" };

List<string> archivedFiles = new List<string>{
    @"G:\Archive\000-007_20230526175817297.zip",
    @"G:\Archive\002443_20230526183639562.zip",
    @"G:\Archive\002446_20230526183334407.zip",
    @"G:\Archive\14967_20240703150011899.zip",
    @"G:\Archive\214_20231213150003676.zip",
    @"G:\Archive\34277_20230526200048891.zip",
    @"G:\Archive\48083_20240214150011919.zip" };

请注意,中的每个元素archivedFiles都是 ZIP 文件的完整路径,其名称以 开头,patientID位于localPatientsIDs或中remotePatientsIDs

例如:@"G:\Archive\000-007_20230526175817297.zip":文件名000-007_20230526175817297.zip以 开头000-007,它是列表中的一个元素remotePatientsIDs

患者 ID 不能同时位于 和localPatientsIDsarchivedFiles因此这两个列表之间不允许有重复项。但是archivedFiles可以包含也位于 中的患者 ID remotePatientsIDs

我需要获取文件名以 中存在但不存在于 中archivedFiles的元素开头的元素。终点是将这些文件解压缩到包含数据库的目录中。remotePatientsIDslocalPatientsIDslocalPatientsIDs

对于给定的示例,我期望得到以下结果:

archivedFilesToUnzip == {
    @"G:\Archive\000-007_20230526175817297.zip",
    @"G:\Archive\002443_20230526183639562.zip",
    @"G:\Archive\002446_20230526183334407.zip",
    @"G:\Archive\214_20231213150003676.zip" }

那么,如何使用 LINQ 来做到这一点?

由于我缺乏知识,我期望它会简单到如下程度:

List<string> archivedFilesToUnzip = archivedFiles.Where(name => name.Contains(remotePatients.Except(localPatients)))

我甚至无法编译它,因为Contains可能无法遍历列表成员,并且我收到消息:

CS1503: Argument 1: cannot convert from 'System.Collections.Generic.IEnumerable<string>' to 'string'

那么,到目前为止,我最好的尝试是以下句子(我承认它对我来说似乎有点混乱)。它总是返回一个空列表。

List<string> archivedFilesToUnzip = archivedFiles.Where(name => archivedFiles.Any(x => x.ToString().Contains(remotePatients.Except(localPatients).ToString()))).ToList();

我发现这些有用的帖子帮助我更好地理解Where和之间的区别Select

  • Entity Framework

另外,我一直在寻找使用 LINQ 的任何方向:

以及其他链接,但我仍然找不到可行的解决方案。

3

  • 2
    值得注意的是,以下两个答案都是首先从文件名中提取患者 ID,然后检查匹配项,而不是尝试使用类似 的测试来匹配整个文件路径.Where(filename => missingPatientsIDs.Any(pid => filename.Contains(pid)。后者可能导致错误匹配,例如在文件夹路径或时间戳中找到 PID,或部分匹配,例如000-007匹配1000-0071


    – 

  • 感谢@TN 的重要考虑!只是为了确认一下,当您说“后者可能导致错误匹配”时,您指的是 Contains 方法,对吗? .Where(filename => missingPatientsIDs.Any(pid => filename.Contains(pid)


    – 


  • 1
    是的。没错。string.Contains()允许部分匹配,但会产生错误匹配。下面的答案使用collection.Contains(),这是需要精确匹配的另一种动物。


    – 



最佳答案
3

C# 是静态(且大多是强)类型语言(如果您想深入了解,请参阅问题和文章)。这意味着编译器将检查变量类型,并且不会允许很多错误,例如比较字符串和布尔值。

remotePatients.Except(localPatients)是 的集合,stringnamearchivedFiles.Where(name => name“仅仅” 一个stringContains字符串 可以接受char(a 中的符号string) 或另一个string,而不是字符串的集合,因此会出现编译错误。

您的第二次尝试可以编译,但不会实现任何有意义的结果 – 如果您分配remotePatients.Except(localPatients).ToString()给变量并检查它或者将结果打印到控制台,您将只看到类型名称(System.Linq.Enumerable+<ExceptIterator>d__99确切地说是 1[System.String]`),这显然不是文件名的一部分。

对于您的问题,我建议您做以下事情:

// build the diff hashset for quick lookup for ids to add
// will improve performance if there are "many" ids
var missing = remotePatients.Except(localPatients)
    .ToHashSet();

// regular expression to extract id from the file name
// you can implement this logic without regex if needed
var regex = new Regex(@"\\(?<id>[\d-]+)_\d+\.zip");

// the result
List<string> archivedFilesToUnzip = archivedFiles
    .Where(name =>
    {
        var match = regex.Match(name); // check the file name for id
        if (match.Success) // id found
        {
            // extract the id from the file name
            var id = match.Groups["id"].Value; 
            return missing.Contains(id); // check if it should be added
        }

        // failed to match pattern for id
        // probably can throw error here to fix the pattern or check the file name
        return false;
    })
    .ToList();

这使用从文件名中提取 id,然后在“缺失”的 id 中搜索它。

这个特定正则表达式的解释可以在找到。

1

  • 2
    +1 使用散列集。我正要使用查询语法和发布类似的逻辑let id = filename.Split(@'\').Last().Split(@'_').First(),但想法是一样的,所以不值得竞争答案。-var archivedFilesToUnzip = (from filename in archivedFiles let id = filename.Split(@'\').Last().Split(@'_').First() where missing.Contains(id) select filename).ToList();


    – 

您可以尝试这个 LINQ 查询,它会返回预期的结果:

using System.Text.RegularExpressions;

List<string> localPatientsIDs = new List<string>
    { "1550615", "1688", "1760654", "1940629", "34277", "48083" };

List<string> remotePatientsIDs = new List<string>
    { "000-007", "002443", "002446", "214", "34277", "48083" };

List<string> archivedFiles = new List<string>
{
    @"G:\Archive\000-007_20230526175817297.zip",
    @"G:\Archive\002443_20230526183639562.zip",
    @"G:\Archive\002446_20230526183334407.zip",
    @"G:\Archive\14967_20240703150011899.zip",
    @"G:\Archive\214_20231213150003676.zip",
    @"G:\Archive\34277_20230526200048891.zip",
    @"G:\Archive\48083_20240214150011919.zip"
};

// a helper function
var getPatientId = (string input) =>
{
    string pattern = @"\\([^\\_]+)_"; // an appropriate pattern
    Match match = Regex.Match(input, pattern);
    return match.Success ? match.Groups[1].Value : null;
};

var query = from file in archivedFiles
    // elements present in remotePatientsIDs
    where remotePatientsIDs.Contains(getPatientId(file))
          // but not in localPatientsIDs
          && !localPatientsIDs.Contains(getPatientId(file))
    select file;

foreach (var file in query)
    Console.WriteLine(file);

下面是一个可以完成您想要的操作的简单 LINQ 查询:

var filtered = archivedFiles
    .Where(file => localPatientsIDs.Any(Path.GetFileName(file).StartsWith))
    .ToArray();

以下是一些细节:

  • localPatientsIDs.Any(Path.GetFileName(file).StartsWith)将检查是否有localPatientsIDs符合条件的项目,即:

    • Path.GetFileName(file).StartsWithPath.GetFileName将从路径中获取文件名,StartsWith并检查文件名是否以任何元素开头localPatientsIDs

为了更清楚,我将定义这种方法:

bool FileNameBeginsWithItem(string filePath, IEnumerable<string> prefixes)
{
    var fileName = Path.GetFileName(filePath);
    return prefixes.Any(fileName.StartsWith);
}

然后你可以像这样使用它:

var filtered1 = archivedFiles
    .Where(file => FileNameBeginsWithItem(file, localPatientsIDs))
    .ToArray();

var filtered2 = archivedFiles
    .Where(file => FileNameBeginsWithItem(file, remotePatientsIDs))
    .ToArray();

3

  • 感谢 @michał-turczyn 的解释。我实现了您建议的 LINQ 查询,它似乎正常工作。我只需要用 替换file => localPatientsIDs.Any(Path.GetFileName(file).StartsWith)file => remotePatients.Any(Path.GetFileName(file).StartsWith)因为我正在寻找archivedFiles文件名以 中存在remotePatientsIDs但不存在于 中的元素开头的元素。顺便说一句localPatientsIDs,我不知道我可以使用这个语法。StartsWith


    – 

  • 2
    @fabricioLima 注意,如果您有带名称的文件2141_...,并且只需要添加 id 的文件214,而不需要 ,则此方法可能会“失败” 2141。这就是我没有添加此类解决方案而是使用正则表达式的原因。但是,如果您始终具有固定的 id 长度(根据数据似乎并非如此),那么这是一种更简单的方法。


    – 


  • @guru-stron,现在我明白了区别,也理解了正则表达式的必要性,这也是 dmartinezfernandez 提出的。我会尝试实现它们。我不是专业开发人员,所以很难预测潜在的失败。这就是为什么这种讨论对我来说非常重要!谢谢大家!


    –