【问题标题】:Getting grouped date range using linq使用 linq 获取分组日期范围
【发布时间】:2013-10-18 14:19:21
【问题描述】:

我有这些数据,我必须按价格分组,并检查数据的范围和连续性

date            price
2014-01-01  10
2014-01-02  10
2014-01-03  10
2014-01-05  20
2014-01-07  30
2014-01-08  40
2014-01-09  50
2014-01-10  30

输出应该是这样的

2014-01-01  2014-01-03  10
2014-01-05  2014-01-05  20
2014-01-07  2014-01-07  30
2014-01-08  2014-01-08  40
2014-01-09  2014-01-09  50
2014-01-10  2014-01-10  30

我试过了

 var result = list
                .OrderBy(a => a.Date)
                .GroupBy(a => a.Price)
                .Select(x => new
                {
                    DateMax = x.Max(a => a.Date),
                    DateMin = x.Min(a => a.Date),
                    Count = x.Count()
                })
                .ToList()
                .Where(a => a.DateMax.Subtract(a.DateMin).Days == a.Count)
                .ToList();

我不确定这是否会处理连续日期。所有日期都是独一无二的!

【问题讨论】:

  • 因此,如果价格在一天和两天后发生变化,它又是旧的,因为两个日期之间的价格不同,您希望以相同的价格获得两组?
  • 我不确定这是否会处理连续日期。所有日期都是独一无二的。这是什么意思?
  • 您可以删除OrderBy 和第一个ToList 而无需更改任何功能。
  • 为什么ToList() 两次?我不确定a.DateMax.Subtract(a.DateMin).Days == a.Count
  • Combining consecutive dates into ranges 的完全相同的副本。几个小时前问过!

标签: c# linq group-by


【解决方案1】:

首先我们将使用辅助方法对连续的项目进行分组。它将采用一个函数,该函数将获得“上一个”和“当前”项,然后它将确定该项是否应该在当前组中,或者应该开始一个新组。

public static IEnumerable<IEnumerable<T>> GroupWhile<T>(
    this IEnumerable<T> source, Func<T, T, bool> predicate)
{
    using (var iterator = source.GetEnumerator())
    {
        if (!iterator.MoveNext())
            yield break;

        List<T> list = new List<T>() { iterator.Current };

        T previous = iterator.Current;

        while (iterator.MoveNext())
        {
            if (predicate(previous, iterator.Current))
            {
                list.Add(iterator.Current);
            }
            else
            {
                yield return list;
                list = new List<T>() { iterator.Current };
            }

            previous = iterator.Current;
        }
        yield return list;
    }
}

现在我们可以使用该方法对项目进行分组,然后选择我们需要的信息:

var query = data.OrderBy(item => item.Date)
                .GroupWhile((previous, current) => 
                    previous.Date.AddDays(1) == current.Date
                    && previous.Price == current.Price)
                .Select(group => new
                {
                    DateMin = group.First().Date,
                    DateMax = group.Last().Date,
                    Count = group.Count(),
                    Price = group.First().Price,
                });

【讨论】:

  • 我一直想知道如何以一种高效且通用的方式进行 GroupWhile,Servy you are too good。
  • 我可以把扩展方法中间的代码改成if (!predicate(previous, iterator.Current)) { yield return list; list = new List&lt;T&gt;() { iterator.Current }; } list.Add(iterator.Current); previous = iterator.Current; 这样只有一个if。 imo 减少了一行代码,但更具可读性。我正在提交编辑。不喜欢就回滚吧
【解决方案2】:

作为Servy's answer 的替代品,我觉得它更优雅,显然更可重复使用,

您可以在一次扫描中做一些更定制的事情(订购后)。

 public class ContiguousValuePeriod<TValue>
 {
     private readonly DateTime start;
     private readonly DateTime end;
     private readonly TValue value;

     public ContiguousValuePeriod(
             DateTime start,
             DateTime end,
             TValue value)
     {
         this.start = start;
         this.end = end;
         this.value = value;
     }

     public DateTime Start { get { return this.start; } }
     public DateTime End { get { return this.start; } }
     public TValue Value { get { return this.value; } }
 }

 public IEnumerable<ContiguousValuePeriod<TValue>>
                     GetContiguousValuePeriods<TValue, TItem>(
         this IEnumerable<TItem> source,
         Func<TItem, DateTime> dateSelector,
         Func<TItem, TValue> valueSelector)
 {
     using (var iterator = source
             .OrderBy(t => valueSelector(t))
             .ThenBy(t => dateSelector(t))
             .GetEnumerator())
     {
         if (!iterator.MoveNext())
         {
             yield break;
         }

         var periodValue = valueSelector(iterator.Current); 
         var periodStart = dateSelector(iterator.Current);
         var periodLast = periodStart;
         var hasTail = false;

         while (iterator.MoveNext())
         {
              var thisValue = valueSelector(iterator.Current);
              var thisDate = dateSelector(iterator.Current);

              if (!thisValue.Equals(periodValue)  ||
                   thisDate.Subtract(periodLast).TotalDays > 1.0)
              {
                  // Period change
                  yield return new ContiguousValuePeriod(
                      periodStart,
                      periodLast,
                      periodValue);
                  periodStart = thisDate;
                  periodValue = thisValue;
                  hasTail = false;
              }
              else
              {
                  hasTail = true;
              }

              periodLast = thisDate;
          }
      }

      if (hasTail)
      {
          yield return new ContiguousValuePeriod(
                      periodStart,
                      periodLast,
                      periodValue);
      }
  }

你喜欢用哪个,

var result = yourList.GetContiguousValuePeriods(
    a => a.Date,
    a => a.Price);

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2021-06-09
    • 2013-07-13
    • 2019-02-23
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-11-04
    相关资源
    最近更新 更多