如何在linq中加入连续日期范围

时间:2019-07-10 04:54:21

标签: c# .net entity-framework linq lambda

所以我有这些数据示例

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]
]
  • prj A和B被分组,因为日期范围prj A与prj重叠 B
  • 因为结束日期prj B和开始日期prj C之间有空格,所以 prj C在不同的组中
  • prj D和E被分组,因为开始日期 prj E是结束日期prj D的第二天

那么如何使用linq lambda创建类似的分组? 谢谢

3 个答案:

答案 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 函数。它需要orderBykeySelector才能确定要评估的相邻值。 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));