Как присоединиться к последовательным датам в linq - PullRequest
3 голосов
/ 10 июля 2019

поэтому у меня есть эти данные пример

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 ]

1 голос
/ 10 июля 2019

Вы можете написать метод расширения, придерживаясь стиля 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
}
1 голос
/ 10 июля 2019

Вы не можете GroupBy их через Linq, потому что у каждого элемента в группе может не быть общего ключа группировки среди других, или он не может быть найден, вместо этого существует только один общий ключ между двумя окрестностями. Попробуйте другой подход:

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
0 голосов
/ 10 июля 2019

Используя вариант оператора сканирования APL (например, Aggregate, который возвращает только промежуточные результаты), который работает с парами элементов, вы можете создать GroupBy, который принимает предикат лямбда-выражений по парам и создает группы до тех пор, покаего значение истинно:

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));
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...