用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() });
这看起来很不错:
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(),
};
}