Warning: file_get_contents(/data/phpspider/zhask/data//catemap/2/.net/20.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
C# 使用LINQ构建字典_C#_.net_Linq_Dictionary_.net Core - Fatal编程技术网

C# 使用LINQ构建字典

C# 使用LINQ构建字典,c#,.net,linq,dictionary,.net-core,C#,.net,Linq,Dictionary,.net Core,假设我们有一个变量“data”,它是Id和子Id的列表: var data = new List<Data> { new() { Id = 1, ChildIds = new List<int> {123, 234, 345} }, new() { Id = 1, ChildIds = new List<int> {123, 234, 345} },

假设我们有一个变量“data”,它是Id和子Id的列表:

var data = new List<Data>
{
    new()
    {
        Id = 1,
        ChildIds = new List<int> {123, 234, 345}
    },
    new()
    {
        Id = 1,
        ChildIds = new List<int> {123, 234, 345}
    },
    new()
    {
        Id = 2,
        ChildIds = new List<int> {678, 789}
    },
};
我想要一本有ChildId和相关Id的字典。如果字典中已存在ChildId,则应使用新Id覆盖

目前我有以下代码:

var dict = new Dictionary<int, int>();
foreach (var dataItem in data)
{
    foreach (var child in dataItem.ChildIds)
    {
        dict[child] = dataItem.Id;
    }
}
这很好,但我不喜欢使用两个循环。我更喜欢使用Linq ToDictionary以功能性的方式构建字典。 使用Linq建立字典的最佳方法是什么


为什么??我更喜欢函数式代码而不是变异状态。除此之外,我只是好奇如何使用Linq建立字典-

使用LINQ,您可以实现如下结果:

Dictionary dict=来自数据中的项 从item.childId中的childId 选择新{item.Id,childId} 不同的 .ToDictionarykv=>kv.childId,kv=>kv.Id; 更新:

与foreach循环完全兼容的版本将使用group by with Last,而不是Distict:

Dictionary dict2=来自数据中的项 从item.childId中的childId 按childId将新的{item.Id,childId}分组到g中 选择g.Last .ToDictionarykv=>kv.childId,kv=>kv.Id;
正如一些人已经指出的,依赖于输入元素的顺序感觉不起作用。LINQ表达式比原始的foreach循环更加复杂。

使用LINQ,您可以获得如下结果:

Dictionary dict=来自数据中的项 从item.childId中的childId 选择新{item.Id,childId} 不同的 .ToDictionarykv=>kv.childId,kv=>kv.Id; 更新:

与foreach循环完全兼容的版本将使用group by with Last,而不是Distict:

Dictionary dict2=来自数据中的项 从item.childId中的childId 按childId将新的{item.Id,childId}分组到g中 选择g.Last .ToDictionarykv=>kv.childId,kv=>kv.Id;
正如一些人已经指出的,依赖于输入元素的顺序感觉不起作用。LINQ表达式比原始的foreach循环更加复杂。

方法是创建Id和ChildId的每个组合的对,并构建这些组合的字典:

        var list = new List<(int Id, int[] ChildIds)>()
        {
            (1, new []{10, 11}),
            (2, new []{11, 12})
        };

        var result = list
            .SelectMany(pair => pair.ChildIds.Select(childId => (childId, pair.Id)))
            .ToDictionary(p => p.childId, p => p.Id);

该方法是为Id和ChildId的每个组合创建一对,并构建一个字典:

        var list = new List<(int Id, int[] ChildIds)>()
        {
            (1, new []{10, 11}),
            (2, new []{11, 12})
        };

        var result = list
            .SelectMany(pair => pair.ChildIds.Select(childId => (childId, pair.Id)))
            .ToDictionary(p => p.childId, p => p.Id);

SelectMany linq运算符实际上有一些不太为人所知的重载。其中一个有一个结果收集器,它是您的场景的完美用例

下面是将其转换为字典的示例代码段。请注意,我必须使用Distinct,因为您有两个值为1的id,其中有一些重复的子id,这会给字典带来问题

void Main()
{
    // Get the data
    var list = GetData();
    
    // Turn it into a dictionary
    var dict = list
        .SelectMany(d => d.ChildIds, (data, childId) => new {data.Id, childId})
        .Distinct()
        .ToDictionary(x => x.childId, x => x.Id);

    // show the content of the dictionary
    dict.Keys
        .ToList()
        .ForEach(k => Console.WriteLine($"{k} {dict[k]}"));
}

public List<Data> GetData()
{
    return 
        new List<Data>
        {
            new Data
            {
                Id = 1,
                ChildIds = new List<int> {123, 234, 345}
            },
            new Data
            {
                Id = 1,
                ChildIds = new List<int> {123, 234, 345}
            },
            new Data
            {
                Id = 2,
                ChildIds = new List<int> {678, 789}
            },
        };
}

public class Data
{
    public int Id { get; set; }
    public List<int> ChildIds { get; set; }
}

SelectMany linq运算符实际上有一些不太为人所知的重载。其中一个有一个结果收集器,它是您的场景的完美用例

下面是将其转换为字典的示例代码段。请注意,我必须使用Distinct,因为您有两个值为1的id,其中有一些重复的子id,这会给字典带来问题

void Main()
{
    // Get the data
    var list = GetData();
    
    // Turn it into a dictionary
    var dict = list
        .SelectMany(d => d.ChildIds, (data, childId) => new {data.Id, childId})
        .Distinct()
        .ToDictionary(x => x.childId, x => x.Id);

    // show the content of the dictionary
    dict.Keys
        .ToList()
        .ForEach(k => Console.WriteLine($"{k} {dict[k]}"));
}

public List<Data> GetData()
{
    return 
        new List<Data>
        {
            new Data
            {
                Id = 1,
                ChildIds = new List<int> {123, 234, 345}
            },
            new Data
            {
                Id = 1,
                ChildIds = new List<int> {123, 234, 345}
            },
            new Data
            {
                Id = 2,
                ChildIds = new List<int> {678, 789}
            },
        };
}

public class Data
{
    public int Id { get; set; }
    public List<int> ChildIds { get; set; }
}
有一个SelectMany的集合,它不仅可以使集合变得平坦,还可以让您获得任何形式的结果

var all = data.SelectMany(
     data => data.ChildIds, //collectionSelector
     (data, ChildId) => new { data.Id, ChildId } //resultSelector 
);
现在,如果要将所有内容转换为字典,必须首先删除重复的childId。您可以使用如下所示的GroupBy,然后从每个组中选择最后一项,正如您在问题中所述,您希望在执行时覆盖ID。字典的键也应该是unique=ChildId:

或者,您可以编写一个实现了IEquatable的新类,并将其用作resultSelector的返回类型,而不是new{data.Id,ChildId}。然后写入all.Reverse.Distinct.ToDictionaryx=>x.ChildId;因此,它将根据您自己的Equals方法实现来检测重复项。相反,因为您说您想要最后一次出现的重复项。

有一个SelectMany的集合,它不仅使集合平坦,而且允许您获得任何形式的结果

var all = data.SelectMany(
     data => data.ChildIds, //collectionSelector
     (data, ChildId) => new { data.Id, ChildId } //resultSelector 
);
现在,如果要将所有内容转换为字典,必须首先删除重复的childId。您可以使用如下所示的GroupBy,然后从每个组中选择最后一项,正如您在问题中所述,您希望在执行时覆盖ID。字典的键也应该是unique=ChildId:

或者,您可以编写一个实现了IEquatable的新类,并将其用作resultSelector的返回类型,而不是new{data.Id,ChildId}。然后写入all.Reverse.Distinct.ToDictionaryx=>x.ChildId;因此,它将根据您自己的Equals方法实现来检测重复项。反向,bec
因为您说过希望最后一次出现重复项。

在这种情况下,您的foreach appproach既可读又高效。所以即使我是LINQ的粉丝,我也会用它。循环的好处是,您可以轻松调试它,或者在必要时添加日志记录,例如无效id

但是,如果您想使用LINQ,我可能会使用and。前者用于展平子集合(如ChildId),后者用于创建与词典非常相似的集合。但一个区别是,它允许重复键,在这种情况下,您可以获得多个值:

ILookup<int, int> idLookup = data
    .SelectMany(d => d.ChildIds.Select(c => (Id:d.Id, ChildId:c)))
    .ToLookup(x => x.ChildId, x => x.Id);
现在您已经拥有了所需的一切,因为它可以像字典一样使用,具有相同的查找性能。如果仍要创建该词典,可以使用:

Dictionary<int, int> dict = idLookup.ToDictionary(x => x.Key, x => x.First());   
如前所述,如果要用新Id覆盖重复项,只需使用Last


.NET FIDLE:

在这种情况下,您的foreach appproach既可读又高效。所以即使我是LINQ的粉丝,我也会用它。循环的好处是,您可以轻松调试它,或者在必要时添加日志记录,例如无效id

但是,如果您想使用LINQ,我可能会使用and。前者用于展平子集合(如ChildId),后者用于创建与词典非常相似的集合。但一个区别是,它允许重复键,在这种情况下,您可以获得多个值:

ILookup<int, int> idLookup = data
    .SelectMany(d => d.ChildIds.Select(c => (Id:d.Id, ChildId:c)))
    .ToLookup(x => x.ChildId, x => x.Id);
现在您已经拥有了所需的一切,因为它可以像字典一样使用,具有相同的查找性能。如果仍要创建该词典,可以使用:

Dictionary<int, int> dict = idLookup.ToDictionary(x => x.Key, x => x.First());   
如前所述,如果要用新Id覆盖重复项,只需使用Last



.NET Fiddle:

您正在寻找SelectMany吗?这很好,但我不喜欢使用两个循环。我更喜欢使用Linq ToDictionary以功能性的方式构建字典为什么,你的方法有什么问题?您将如何使用这些数据?您是否尝试过Linq,如果没有,请尝试一下,如果您有任何具体问题,请告诉我们。如果你有,发布相关的Linq,我们可以进一步提供帮助。你的方法很好,Linq唯一能做的就是把你已经在做的事情抽象出来。除了额外的错误更正之外,我认为您的实现没有任何问题。这段代码可以工作,但我更喜欢函数代码,而不是改变状态。除此之外,我只是好奇如何使用Linq建立字典-@zaggler完全一样,但是我喜欢Linq的简洁性和可读性。你在找SelectMany吗?这很好,但我不喜欢使用两个循环。我更喜欢使用Linq ToDictionary以功能性的方式构建字典为什么,你的方法有什么问题?您将如何使用这些数据?您是否尝试过Linq,如果没有,请尝试一下,如果您有任何具体问题,请告诉我们。如果你有,发布相关的Linq,我们可以进一步提供帮助。你的方法很好,Linq唯一能做的就是把你已经在做的事情抽象出来。除了额外的错误更正之外,我认为您的实现没有任何问题。这段代码可以工作,但我更喜欢函数代码,而不是改变状态。除此之外,我只是好奇如何使用Linq建立字典-@zaggler完全一样,但是我喜欢Linq的简洁性和可读性。感谢您的解决方案,尽管我得到了以下异常:System.ArgumentException:“已经添加了具有相同密钥的项。如果字典中已经有子项,则需要覆盖,在构建字典之前,此LINQ可能应该先执行GroupBy,然后执行Last,以便从重复项中选择最后一项。@缺少Rogier Distinct,因为您有重复的数据。没错,Equals比较匿名类型中的值。但是,如果在dataYes中将678替换为123,代码仍会引发异常。感谢您的解决方案,尽管我遇到了以下异常:System.ArgumentException:'已添加具有相同键的项。如果字典中已有子项,则需要覆盖,在构建字典之前,此LINQ可能应该先执行GroupBy,然后执行Last,以便从重复项中选择最后一项。@缺少Rogier Distinct,因为您有重复的数据。没错,Equals比较匿名类型中的值。然而,如果您在数据中将678替换为123,您的代码仍然抛出异常。是的,您的第二个示例有效。这是一个极好的答案。我在SelectMany中没有注意到这个重载。如果使用稍微不同的数据集,这段代码会引发异常。把数据中的678替换为123,这是一个很好的答案。我在SelectMany中没有注意到这个重载。如果使用稍微不同的数据集,这段代码会引发异常。只需将数据中的678替换为123。这应标记为正确答案。所有其他答案要么都是错的,要么都是错的
od。这应标记为正确答案。所有其他答案要么是错误的,要么不是那么好。