【问题标题】:Combining consecutive dates into ranges将连续日期组合成范围
【发布时间】:2013-10-17 17:16:47
【问题描述】:

我有一个对象列表

public class sample
{
 public DateTime Date;
 public string content;
}

我希望能够创建一个新对象列表

public class sampleWithIntervals
{
 public DateTime startDate;
 public DateTime endDate;
 public string content;
}

应根据内容将示例对象分组为间隔。间隔只能包括原始样本列表中包含的那些日期。 我不知道如何在 Linq 中做到这一点。

样本数据:

{"10/1/2013", "x"}
{"10/2/2013", "x"}
{"10/2/2013", "y"}
{"10/3/2013", "x"}
{"10/3/2013", "y"}
{"10/10/2013", "x"}
{"10/11/2013", "x"}
{"10/15/2013", "y"}
{"10/16/2013", "y"}
{"10/20/2013", "y"}

This should give me 
{"10/1/2013","10/3/2013", "x"}
{"10/2/2013","10/3/2013", "y"}
{"10/10/2013","10/11/2013", "x"}
{"10/15/2013","10/16/2013", "y"}
{"10/20/2013","10/20/2013", "y"}

【问题讨论】:

  • 如果连续范围同时具有 x 和 y 怎么办?那么他们应该分成两组吗?
  • 必须在 Linq 中吗?一个简单的循环会更加更简洁。
  • 循环也可以。是的,他们应该在两个不同的组中

标签: c# linq c#-4.0


【解决方案1】:

这是一种非 Linq 方法:

List<sampleWithIntervals> groups = new List<sampleWithIntervals>();  
sampleWithIntervals curGroup = null;

foreach(sample s in samples.OrderBy(sa => sa.content).ThenBy(sa => sa.Date))
{
    if(curGroup == null || // first group
        s.Date != curGroup.endDate.AddDays(1) ||
        s.content != curGroup.content   // new group
      ) 
    {
        curGroup = new sampleWithIntervals() {startDate = s.Date, endDate = s.Date, content = s.content};
        groups.Add(curGroup);
    }
    else
    {
        // add to current group
        curGroup.endDate = s.Date;
    }
}

您可以使用 Linq 使用按日期减去索引对项目进行分组来对连续项目进行分组的技巧来做到这一点:

samples.OrderBy(s => s.content)   
       .ThenBy(s => s.Date)
       // select each item with its index
       .Select ((s, i) => new {sample = s, index = i})  
       // group by date miuns index to group consecutive items
       .GroupBy(si => new {date = si.sample.Date.AddDays(-si.index), content = si.sample.content})  
       // get the min, max, and content of each group
       .Select(g => new sampleWithIntervals() {
                        startDate = g.Min(s => s.sample.Date), 
                        endDate = g.Max(s => s.sample.Date), 
                        content = g.First().sample.content
                        })

【讨论】:

  • 当同一日期有两个不同内容的样本时,这将不起作用。它将创建更多间隔。
  • @RaghaJ - 如果您先将samplescontent 排序,然后按Date 排序,这将起作用
【解决方案2】:

我有这个SplitBy 扩展方法,您可以在其中指定用于拆分集合的分隔符谓词,就像string.Split 一样。

public static IEnumerable<IEnumerable<T>> SplitBy<T>(this IEnumerable<T> source, 
                                                     Func<T, bool> delimiterPredicate,
                                                     bool includeEmptyEntries = false, 
                                                     bool includeSeparator = false)
{
    var l = new List<T>();
    foreach (var x in source)
    {
        if (!delimiterPredicate(x))
            l.Add(x);
        else
        {
            if (includeEmptyEntries || l.Count != 0)
            {
                if (includeSeparator)
                    l.Add(x);

                yield return l;
            }

            l = new List<T>();
        }
    }
    if (l.Count != 0 || includeEmptyEntries)
        yield return l;
}

因此,如果您可以指定连续的条纹分隔符,那么现在拆分很容易。为此,您可以订购集合并与相邻项目一起压缩,因此现在两个结果列中的日期差异可以用作分隔符。

var ordered = samples.OrderBy(x => x.content).ThenBy(x => x.Date).ToArray();
var result = ordered.Zip(ordered.Skip(1).Append(new sample()), (start, end) => new { start, end })
                    .SplitBy(x => x.end.Date - x.start.Date != TimeSpan.FromDays(1), true, true)
                    .Select(x => x.Select(p => p.start).ToArray())
                    .Where(x => x.Any())
                    .Select(x => new sampleWithIntervals
                    {
                        content = x.First().content,
                        startDate = x.First().Date,
                        endDate = x.Last().Date
                    });

new sample() 是一个虚拟实例,用于正确获取ZipAppend 方法是将项目附加到IEnumerable&lt;&gt; 序列,它是这样的:

public static IEnumerable<T> Append<T>(this IEnumerable<T> source, params T[] items)
{
    return source.Concat(items);
}

注意:这不会保留初始顺序。如果您想要原始顺序,请首先选择索引并立即形成一个匿名类(Select((x, i) =&gt; new { x, i })),并在最后阶段根据索引进行排序,然后再选择合适的类型。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2014-12-16
    • 2013-03-24
    • 1970-01-01
    • 2013-06-29
    • 2017-02-12
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多