使用LINQ将两个包含嵌套列表的列表合并为一个列表的C#代码

4

我有两个C#列表,其结构如下。<>中的每个名称都是一个列表。我想将这两个列表合并成一个List<Server>。以下伪代码显示了两个这样的列表以及结果应该是什么:

<Servers>                           <Servers>
+...                                +...
+-- Server A,1                      +-- Server A,1
|       +...                        |       +...    
|       +--<Maps>                   |       +--<Maps>
|           +--Map x                |           +--Map x
|               +...                |               +...    
|               +--<Times>          |               +--<Times>
|                   +--Time i       |                   +--Time l
|                   +--Time j       |               +--<Jumps>
|               +--<Jumps>          |                   +--Jump t
|                   +--Jump s       |                   +--Jump u
|           +--Map y                |           +--Map z
|               +...                |               +...
|               +--<Times>          |               +--<Jumps>  
|                   +-- Time k      |                   +--Jump v
+-- Server B,1                      +-- Server B,2

结果应该如下:

<Servers>
+...
+-- Server A,1
|   +...
|   +--<Maps>
|       +-- Map x
|           +...
|           +--<Times>
|               +--Time i
|               +--Time j
|               +--Time l
|           +--<Jumps>
|               +--Jump s
|               +--Jump t
|               +--Jump u
|       +-- Map y
|           +...
|           +--<Times>
|               +--Time k
|       +-- Map z
|           +...
|           +--<Jumps>
|               +--Jump v
+-- Server B,1
+-- Server B,2

我尝试使用LINQ进行全外连接,但结果并不是我想要的。由于我不理解相同键值的服务器没有匹配,因此我总是得到具有不同数据的相同服务器的重复项。那时,我停止了使用LINQ,并使用循环手动合并列表。
下面的代码给了我所需的结果列表。目前我将使用它,直到我找到更好的方法。是否有使用LINQ或Lambda表达式更简单/更短的方法呢?
  foreach (Server importServer in oImportList)
        {
            if (!CoreList.Contains(importServer))
            {
                CoreList.Add(importServer);
                continue;
            }

            Server coreServer = CoreList.FirstOrDefault(o => o.Equals(importServer));
            coreServer.Name = importServer.Name;
            coreServer.KZTimerVersion = importServer.KZTimerVersion;
            foreach(Map importMap in importServer.Maps)
            {
                if (!coreServer.Maps.Contains(importMap))
                {
                    coreServer.Maps.Add(importMap);
                    continue;
                }
                Map coreMap = coreServer.Maps.FirstOrDefault(o => o.Equals(importMap));
                coreMap.Jumps.Concat(importMap.Jumps);
                coreMap.Times.Concat(importMap.Times);
            }
        }

我认为你的想法是正确的。看起来你只是传递了错误的参数给嵌套的Join!例如,在Maps连接中,你试图连接coreServer.MapsoImportList... 我猜这应该是coreServer.MapsimportServer.Maps - 31eee384
在你的帮助下,我成功让代码运行了,但是我发现结果不是我想要的列表。它只包含了两个列表中都存在的元素。 - schm0ftie
啊,抱歉...我很少使用Join,所以搞混了。我恢复了一个使用GroupBySelect的答案,也许适合你。之前我删除它是因为我觉得我走错了方向。 - 31eee384
1个回答

0

你也许一直在寻找.Union,但它并没有处理嵌套列表,所以我认为这不是你真正需要的。我设置了一个完整的例子--虽然结构与你的不同,但我认为你可以很容易地应用核心linq:

using System;
using System.Collections.Generic;
using System.Linq;

public class Map
{
    public string Name { get; set; }
    public IEnumerable<MapProperty> Properties { get; set; }
}

public class MapProperty
{
    public string Name { get; set; }
}

public class Test
{
    public static IEnumerable<Map> Merge(IEnumerable<Map> a, IEnumerable<Map> b)
    {
        return a.Concat(b)
            .GroupBy(map => map.Name)
            .Select(mg => new Map
            {
                Name = mg.Key,
                Properties = mg.SelectMany(v => v.Properties)
                    .GroupBy(p => p.Name)
                    .Select(pg => new MapProperty { Name = pg.Key })
            });
    }

    public static void Main()
    {
        var mapsA = new[]
        {
            new Map
            {
                Name = "MapA",
                Properties = new[]
                {
                    new MapProperty { Name = "Jumps" },
                    new MapProperty { Name = "Hops" },
                }
            },
            new Map
            {
                Name = "MapB",
                Properties = new[]
                {
                    new MapProperty { Name = "Jumps" },
                    new MapProperty { Name = "Skips" },
                }
            }
        };
        var mapsB = new[]
        {
            new Map
            {
                Name = "MapA",
                Properties = new[]
                {
                    new MapProperty { Name = "Jumps" },
                    new MapProperty { Name = "Times" },
                }
            },
            new Map
            {
                Name = "MapC",
                Properties = new[]
                {
                    new MapProperty { Name = "Jumps" },
                    new MapProperty { Name = "Skips" },
                }
            }
        };

        var mapsC = Merge(mapsA, mapsB);

        foreach (var map in mapsC)
        {
            Console.WriteLine($"{map.Name}");
            foreach (var prop in map.Properties)
            {
                Console.WriteLine($"\t{prop.Name}");
            }
        }
    }
}

输出:

MapA
    Jumps
    Hops
    Times
MapB
    Jumps
    Skips
MapC
    Jumps

Merge 是重要的部分。它首先将 ab 放入一个列表中,然后 GroupBy 名称形成具有相同名称的映射组。 Select 获取每个组并使用该组中所有 Maps 的内容创建新的 Map(这是合并!)。然后我对新合并地图的内容执行了相同的操作,并将 MapProperty 分组并组合/合并。

在您的特定情况下,您需要更多的工作和级别来合并所有内容。我建议找到一种方法为树中的每个级别制作一个 Merge 方法,并从每个父级别调用下一个级别的方法,以便您不会得到一个巨大的 linq 链。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接