所以我有这些数据示例
projects startdate enddate
prj A 01 Jan 2019 10 Feb 2019
prj B 29 Jan 2019 15 Mar 2019
prj C 21 Mar 2019 02 May 2019
prj D 07 May 2019 10 Jun 2019
prj E 11 Jun 2019 30 Jul 2019
并且我想使用linq lambda使每个对象包含连续项目列表的列表对象 分组结果大致是这样
[
[prj A, prj B],
[prj C],
[prj D, prj E]
]
那么如何使用linq lambda创建类似的分组? 谢谢
答案 0 :(得分:1)
您不能通过Linq GroupBy
来使用它们,因为组中的每个项目可能在其他项目中没有公共的分组密钥,或者找不到,而是在两个邻域之间仅存在一个公共密钥。 Try another approach:
var result = new List<List<string>>();
var projects = new List<Project>
{
new Project{ Name = "prj A",
startdate = new DateTime(2019, 1, 1), enddate = new DateTime(2019, 2, 10) },
new Project{ Name = "prj B",
startdate = new DateTime(2019, 1, 29), enddate = new DateTime(2019, 3, 15) },
new Project{ Name = "prj C",
startdate = new DateTime(2019, 3, 21), enddate = new DateTime(2019, 5, 2) },
new Project{ Name = "prj D",
startdate = new DateTime(2019, 5, 7), enddate = new DateTime(2019, 6, 10) },
new Project{ Name = "prj E",
startdate = new DateTime(2019, 6, 11), enddate = new DateTime(2019, 7, 30) }
};
projects = projects.OrderBy(x => x.startdate).ToList();
for (var i = 0; i < projects.Count; i++)
{
var project = projects[i];
if (projects[0] != project
&&
(project.startdate - projects[i - 1].enddate).TotalDays < 2)
result.Last().Add(project.Name);
else
result.Add(new List<string> { project.Name });
}
foreach(var gr in result)
Console.WriteLine(String.Join(", ", gr));
//prj A, prj B
//prj C
//prj D, prj E
答案 1 :(得分:1)
您可以编写一个遵循LINQ样式的扩展方法。 JoinBy
充当分析 lag 函数。它需要orderBy
和keySelector
才能确定要评估的相邻值。 join
用于确定何时可以连接相邻的值。
public static class LinqExtension
{
public static IEnumerable<IEnumerable<TSource>> JoinBy<TSource, TOrderKey, TKey>(
this IEnumerable<TSource> source,
Func<TSource, TOrderKey> orderBy,
Func<TSource, TKey> keySelector,
Func<TKey, TKey, bool> join)
{
var results = new List<List<TSource>>();
var orderedSource = new List<TSource>(source).OrderBy(orderBy).ToArray();
if (orderedSource.Length > 0)
{
var group = new List<TSource> { orderedSource[0] };
results.Add(group);
if (orderedSource.Length > 1)
{
for (int i = 1; i < orderedSource.Length; i++)
{
var lag = orderedSource[i - 1];
var current = orderedSource[i];
if (join(keySelector(lag), keySelector(current)))
{
group.Add(current);
}
else
{
group = new List<TSource> { current };
results.Add(group);
}
}
}
}
return results;
}
}
..并将其用于将相邻项目组合在一起。
public class Project
{
public string Name { get; set; }
public DateTime Begin { get; set; }
public DateTime End { get; set; }
}
[TestMethod]
public void TestCase1()
{
var projects = new List<Project>() {
new Project { Name = "A", Begin = new DateTime(2000, 1, 1), End = new DateTime(2000, 12, 31) }
, new Project { Name = "B", Begin = new DateTime(2001, 1, 1), End = new DateTime(2001, 12, 31) }
, new Project { Name = "C", Begin = new DateTime(2010, 1, 1), End = new DateTime(2010, 12, 31) }
, new Project { Name = "D", Begin = new DateTime(2010, 6, 1), End = new DateTime(2010, 7, 1) }
};
var grouped = projects.JoinBy(
x => x.Begin,
x => (begin: x.Begin, end: x.End),
(x1, x2) => x2.begin <= x1.end.AddDays(1) && x1.begin <= x2.end.AddDays(1));
var builder = new StringBuilder();
foreach (var grp in grouped)
{
builder.AppendLine(string.Join(", ", grp.Select(x => x.Name)));
}
var rendered = builder.ToString();
// rendered =>
// A, B
// C, D
}
编辑:评论中的另一个测试用例
[TestMethod]
public void TestCase2_FromComments()
{
var projects = new List<Project>() {
new Project { Name = "A", Begin = new DateTime(2019, 4, 2), End = new DateTime(2019, 8, 17) }
, new Project { Name = "B", Begin = new DateTime(2019, 6, 1), End = new DateTime(2019, 7, 1) }
};
var grouped = projects.JoinBy(
x => x.Begin,
x => (begin: x.Begin, end: x.End),
(x1, x2) => x2.begin <= x1.end.AddDays(1) && x1.begin <= x2.end.AddDays(1));
var builder = new StringBuilder();
foreach (var grp in grouped)
{
builder.AppendLine(string.Join(", ", grp.Select(x => x.Name)));
}
var rendered = builder.ToString();
// rendered =>
// A, B
}
答案 2 :(得分:0)
使用适用于成对项目的APL扫描运算符的变体(例如Aggregate
,仅返回中间结果),您可以创建一个GroupBy
,该谓词对成对使用谓词lambda并创建只要其值为true,就可以进行分组:
public static class IenumerableExt {
// TKey combineFn((TKey Key, T Value) prevKeyItem, T curItem)
// prevKeyItem.Key = Previous Key (initially, seedKey)
// prevKeyItem.Value = Previous Item
// curItem = Current Item
// returns TKey for Current Item
public static IEnumerable<(TKey Key, T Value)> ScanToPairs<T, TKey>(this IEnumerable<T> src, TKey seedKey, Func<(TKey Key, T Value), T, TKey> combineFn) {
using (var srce = src.GetEnumerator())
if (srce.MoveNext()) {
var prevkv = (seedKey, srce.Current);
while (srce.MoveNext()) {
yield return prevkv;
prevkv = (combineFn(prevkv, srce.Current), srce.Current);
}
yield return prevkv;
}
}
// bool testFn(T prevVal, T curVal)
public static IEnumerable<IGrouping<int, T>> GroupPairsByWhile<T>(this IEnumerable<T> src, Func<T, T, bool> testFn) =>
src.ScanToPairs(1, (kvp, cur) => testFn(kvp.Value, cur) ? kvp.Key : kvp.Key + 1)
.GroupBy(kvp => kvp.Key, kvp => kvp.Value);
}
使用此扩展程序,您可以轻松地将项目分组:
var ans = projects.GroupPairsByWhile((prev,cur) => cur.startdate <= prev.enddate.AddDays(1));