使用类似的东西:
IList<DateTime> dates =
this.DateRanges
.SelectMany(r => new [] { r.From, r.To })
.Distinct()
.OrderBy(d => d)
.ToList();
我可以获得所有日期,而不会重复任何日期。范围可能完全重叠,部分重叠(上部或下部重叠),触摸或根本不重叠。
现在我需要将此列表转换为另一个列表,以便每个连续日期对在对中间形成一个新生成的DateTime
实例
D1 D2 D3 D4 D5
G1 G2 G3 G4
D n 是我列表中的不同日期, G m 日期是我的日期喜欢在它们中间生成。
如何将各个日期的有序列表转换为对,以便我得到如下例所示的对?我想用LINQ而不是for
循环来形成这些,这可以完成同样的事情。由于延迟表达式树的执行,使用LINQ可能会产生更高效的代码。
假设这是我的范围的例子:
D1 D2 D3 D4 D5 D6 D11 D12
|--------------| |------| |------| |------|
D7 D8
|--------------------------|
D9 D10
|-----------------------------------------------|
获取不同日期的第一步将导致这些日期:
D1 D7 D2 D3 D4 D5 D6 D10 D11 D12
D9和D8会掉线,因为它们是重复的。
下一步是形成对(我不知道如何使用LINQ执行此操作):
D1-D7, D7-D2, D2-D3, D3-D4, D4-D5, D5-D6, D6-D10, (D10-D11), D11-D12
最后一步必须使用以下方法计算每对的日期:
D new = D from +(D to - D from )/ 2
范围D 10 -D 11 应该优选地省略。但是如果省略它会导致代码过于复杂,那么可以通过单独的检查来保留和排除代码。但如果最初可以将其排除在外那么应该做什么。因此,如果您还提供有关如何形成排除空范围的对的信息,也欢迎您添加该信息。
答案 0 :(得分:5)
您可以使用Zip()
:
var middleDates = dates.Zip(dates.Skip(1),
(a, b) => (a.AddTicks((b - a).Ticks / 2)))
.ToList();
答案 1 :(得分:3)
根据@DavidB的想法和@AakashM的原始答案的有趣想法,我提出了我自己的解决方案,从一组日期中提取范围(同时也省略空范围)和计算范围中间日期。
如果您对此解决方案有任何改进建议或意见,我们热烈欢迎您对此发表评论。无论如何,这是我现在使用的最终代码(内联注释解释其功能):
// counts range overlaps
int counter = 0;
// saves previous date to calculate midrange date
DateTime left = DateTime.Now;
// get mid range dates
IList<DateTime> dates = this.DateRanges
// select range starts and ends
.SelectMany(r => new[] {
new {
Date = r.From,
Counter = 1
},
new {
Date = r.To,
Counter = -1
}
})
// order dates because they come out mixed
.OrderBy(o => o.Date)
// convert dates to ranges; when non-empty & non-zero wide get mid date
.Select(o => {
// calculate middle date if range isn't empty and not zero wide
DateTime? result = null;
if ((counter != 0) && (left != o.Date))
{
result = o.Date.AddTicks(new DateTime((o.Date.Ticks - left.Ticks) / 2).Ticks);
}
// prepare for next date range
left = o.Date;
counter += o.Counter;
// return middle date when applicable otherwise null
return result;
})
// exclude empty and zero width ranges
.Where(d => d.HasValue)
// collect non nullable dates
.Select(d => d.Value)
.ToList();
答案 2 :(得分:1)
下一步是形成对(我不知道如何使用LINQ执行此操作):
List<DateTime> edges = bucketOfDates
.Distinct()
.OrderBy(date => date)
.ToList();
DateTime rangeStart = edges.First(); //ps - don't forget to handle empty
List<DateRange> ranges = edges
.Skip(1)
.Select(rangeEnd =>
{
DateRange dr = new DateRange(rangeStart, rangeEnd);
rangeStart = rangeEnd;
return dr;
})
.ToList();
答案 3 :(得分:1)
好的,我以前的想法是行不通的。但是这个会。它的输入数量为O(n)
。
为了解决D10-D11问题,我们需要让过程知道在任何给定日期有多少原始间隔“有效”。然后我们可以按顺序迭代抛出转换点,并且只要我们在两个转换之间发出中间点和当前状态为ON。这是完整的代码。
数据类:
// The input type
class DateRange
{
public DateTime From { get; set; }
public DateTime To { get; set; }
}
// Captures details of a transition point
// along with how many ranges start and end at this point
class TransitionWithCounts
{
public DateTime DateTime { get; set; }
public int Starts { get; set; }
public int Finishes { get; set; }
}
处理代码:
class Program
{
static void Main(string[] args)
{
// Inputs as per question
var d1 = new DateTime(2011, 1, 1);
var d2 = new DateTime(2011, 3, 1);
var d3 = new DateTime(2011, 4, 1);
var d4 = new DateTime(2011, 5, 1);
var d5 = new DateTime(2011, 6, 1);
var d6 = new DateTime(2011, 7, 1);
var d11 = new DateTime(2011, 9, 1);
var d12 = new DateTime(2011, 10, 1);
var d7 = new DateTime(2011, 2, 1);
var d8 = d5;
var d9 = d1;
var d10 = new DateTime(2011, 8, 1);
var input = new[]
{
new DateRange { From = d1, To = d2 },
new DateRange { From = d3, To = d4 },
new DateRange { From = d5, To = d6 },
new DateRange { From = d11, To = d12 },
new DateRange { From = d7, To = d8 },
new DateRange { From = d9, To = d10 },
};
第一步是将输入的开始和结束捕获为转换点。每个原始范围变为两个转换点,每个转换点的计数为1.
// Transform into transition points
var inputWithBeforeAfter = input.SelectMany(
dateRange => new[]
{
new TransitionWithCounts { DateTime = dateRange.From, Starts = 1 },
new TransitionWithCounts { DateTime = dateRange.To, Finishes = 1 }
});
现在我们按日期对这些进行分组,总结在此日期开始和结束的原始范围的数量
// De-dupe by date, counting up how many starts and ends happen at each date
var deduped = (from bdta in inputWithBeforeAfter
group bdta by bdta.DateTime
into g
orderby g.Key
select new TransitionWithCounts
{
DateTime = g.Key,
Starts = g.Sum(bdta => bdta.Starts),
Finishes = g.Sum(bdta => bdta.Finishes)
}
);
为了处理这个问题,我们可以使用Aggregate
(可能),但是(对我来说)读取和编写手动迭代的速度要快得多:
// Iterate manually since we want to keep a current count
// and emit stuff
var output = new List<DateTime>();
var state = 0;
TransitionWithCounts prev = null;
foreach (var current in deduped)
{
// Coming to a new transition point
// If we are ON, we need to emit a new midpoint
if (state > 0)
{
// Emit new midpoint between prev and current
output.Add(prev.DateTime.AddTicks((current.DateTime - prev.DateTime).Ticks / 2));
}
// Update state
state -= current.Finishes;
state += current.Starts;
prev = current;
}
如果我们感觉到的话,我们最终可以断言state == 0
。
// And we're done
foreach (var dateTime in output)
{
Console.WriteLine(dateTime);
}
// 16/01/2011 12:00:00
// 15/02/2011 00:00:00
// 16/03/2011 12:00:00
// 16/04/2011 00:00:00
// 16/05/2011 12:00:00
// 16/06/2011 00:00:00
// 16/07/2011 12:00:00
// 16/09/2011 00:00:00
// Note: nothing around 15/08 as that is between D10 and D11,
// the only midpoint where we are OFF
Console.ReadKey();