用LINQ实现数据结构的非平坦化

本文关键字:LINQ 实现 数据结构 | 更新日期: 2023-09-27 17:53:47

这不是家庭作业。如果有必要,请随意推荐一个更好的地方来发布这篇文章。

给定以下数据结构:

class Thing
{
    public Thing() 
    {
        this.Things = new List<Thing>();
    }
    public string First { get; set; }
    public string Group { get; set; }
    public List<Thing> Things { get; set; }
}

和以下数据:

var data = new List<Thing>
{
    new Thing { First = "Alex", Group = "Sams" },
    new Thing { First = "John", Group = "Sams" },
    new Thing { First = "", Group = "Sams" },
    new Thing { First = "Sue", Group = "Freds" },
};

把它重新塑造成一个像这样的List的linquest方法是什么?

First = "", Group = Sams
    First = Alex, Group = Sams
    First = John, Group = Sams
First = Sue, Group = Freds

注意事项:

  • 注意Thing可以包含一个Thing列表。
  • 如果没有指定First,则假定该为父记录,并且所有包含First且具有相同的记录都是子记录。
  • 根据上述规则没有父级的记录被视为父级。

这个例子显示了期望的结果:

var result = data.Where(p => p.First == "").Select(p => 
{
    p.Things = data.Where(f => f.Group == p.Group).ToList();
    return p;
}).ToList();
result.AddRange(data.Except(result.SelectMany(f => f.Things)).ToList());

这也达到了预期的结果,但是感觉有点长。

var result = data
    .GroupBy(p => p.Group)
    .SelectMany(p => p.Where(f => f.First == "" || p.Count() == 1))
    .Select(p => new Thing { First = p.First, Group = p.Group, Things = data
        .Where(f => f.Group == p.Group && f.First != "" && p.First != f.First).ToList() });

用LINQ实现数据结构的非平坦化

这看起来很不错:

var result =
    data
        .Where(p => p.First != "")
        .GroupBy(p => p.Group)
        .Select(gps =>
            gps.Count() == 1
            ? gps.First()
            : new Thing()
            {
                First = "",
                Group = gps.Key,
                Things = gps.ToList(),
            })
        .ToList();

这基本上忽略了所有具有First == ""的现有事物,然后将所有剩余的事物按Group分组。然后,它用一个新的父元素重新构建列表,其中组有多个元素。


这是在有"parent"的情况下更健壮的替代方法没有子对象:

var result2 =
    data
        .GroupBy(p => p.Group)
        .Select(gps =>
            gps.Count() == 1
            ? gps.First()
            : new Thing()
            {
                First = "",
                Group = gps.Key,
                Things =
                    gps
                        .Where(p => p.First != "")
                        .ToList(),
            })
        .ToList();

这里有第三个更健壮的选项:

var result3 =
    data
        .OrderBy(p => p.First)
        .GroupBy(p => p.Group)
        .Select(gps =>
            gps
                .Skip(1)
                .Aggregate(
                    gps
                        .Select(p => new Thing()
                        {
                            First = p.First,
                            Group = p.Group
                        })
                        .First(),
                    (a, x) =>
                    {
                        a.Things.Add(x);
                        return a;
                    }))
        .ToList();

我们也可以将这个构造函数添加到Thing:

public Thing(IEnumerable<Thing> things)
{
    this.Things = new List<Thing>(things);
}

现在可以运行了:

var result4 =
(
    from p in data.OrderBy(p => p.First).GroupBy(p => p.Group)
    let f = p.First()
    select new Thing(p.Skip(1)) { First = f.First, Group = f.Group }
).ToList();

无需错误检查或其他考虑。

    static void Main(string[] args)
    {
        var data = new List<Thing> { new Thing { First = "Alex", Group = "Sams" }, new Thing { First = "John", Group = "Sams" }, new Thing { First = "", Group = "Sams" }, new Thing { First = "Sue", Group = "Freds" } };
        var results = from item in data
                      group item by item.Group into g
                      select g;
        List<Thing> list = new List<Thing>();
        foreach (var item in results)
        {
            if (item.Count() > 1)
            {
                Thing parent = item.Where(x => String.IsNullOrEmpty(x.First)).First();
                parent.First = "";
                parent.Group = item.First().Group;
                parent.Things = item.Where(x => x != parent).ToList();
                list.Add(parent);
            }
            else
            {
                list.Add(item.First());
            }
        }
    }

一个语句yes…由于没有多个枚举,因此更高效…

var unflattenedList = data.GroupBy(thing => thing.Group, thing => thing, 
    (key, things) => new Thing()
    {
        First = things.Count() == 1 ? things.First().First : string.Empty, 
        Group = key,
        Things = things.Count() == 1 ? null : 
                     things.Where(t => t.First != string.Empty).ToList(),
    });
编辑:

下面是我要用的。因为这消除了上述解决方案的多重枚举问题。

你的LINQ是这样的…

var unflattenedList = data.GroupBy(thing => thing.Group, thing => thing, GetThing);

返回new Thing的方法在这里…

private static Thing GetThing(string key, IEnumerable<Thing> things)
{
    var thingList = things.ToList();
    return new Thing()
    {
        First = thingList.Count() == 1 ? thingList.First().First : string.Empty, 
        Group = key,
        Things = thingList.Count() == 1 ? null : 
                        thingList.Where(t => t.First != string.Empty).ToList(),
    };
}