具有多个逻辑条件的 foreach 循环
foreach loop with multiple logic conditions
我有一个对象集合,我需要对其进行迭代和处理。到目前为止似乎很容易。但是,我有一些条件使它变得相当复杂。
这是一些信息:
该集合包含一堆 "Planet" 具有行星相位时间的对象。
如果两个阶段之间的时间跨度小于或等于 30 分钟,则行星观看时间将合并为块。
例如这里有6个相位时间:
- 第 1 阶段:8:00am - 9:30am
- 第 2 阶段:10:00am - 11:00am
- 第 3 阶段:11:20am - 12:30pm
- 第 4 阶段:2:00pm - 4:00pm
- 第 5 阶段:6:30pm - 7:30pm
- 第 6 阶段:7:45pm - 9:00pm
根据上面的数据,我们有以下块:
- 第 1 阶段到第 3 阶段:一个连续观看块
- 第 4 阶段:单独的观看区
- 第 5 阶段和第 6 阶段:一个连续观看块
数学:
- (第2阶段开始时间)-(第1阶段结束时间)= 30分钟
- (第三阶段开始时间) - (第二阶段结束时间) = 20分钟
- (第 4 阶段开始时间)-(第 3 阶段结束时间)= 90 分钟
- (第5阶段开始时间)-(第4阶段结束时间)= 150分钟
- (第6阶段开始时间) - (第5阶段结束时间) = 15分钟
到目前为止我失败的尝试:
int i = 0;
bool continueBlocking = false;
foreach (var p in GalaxySector) //IEnumerable
{
//ensure that dates are not null
if (p.StartDatePhase != null || p.EndDatePhase != null) {
if (continueBlocking) {
string planetName = p.Name;
string planetCatalogId = p.CatalogId;
datetime? StartPhase = p.StartDatePhase.Value;
datetime? EndPhase = p.EndDatePhase.Value;
} else {
string planetName = p.Name;
string planetCatalogId = p.CatalogId;
datetime? StartPhase = p.StartDatePhase.Value;
datetime? EndPhase = p.EndDatePhase.Value;
}
if (i < 2) {
continue;
}
TimeSpan? spanBetweenSections = StartPhase - EndPhase;
if ( spanBetweenSections.Value.TotalMinues <= 30) {
continueBlocking = true;
continue;
} else {
CreateSchedule(planetName, planetCatalogId, StartPhase, EndPhase);
continueBlocking = false;
}
}
i++;
}
我在这个愚蠢的循环上花了好几个小时,我认为另一双眼睛会很好。
它 feels/looks 太复杂、太过时、太混乱。有 better/modern 的方法吗?
谢谢!
假设这些是日期而不仅仅是一天中的时间,您可以执行以下操作
var galaxySector = new List<PlanetPhase>
{
new PlanetPhase
{
Name = "Saturn",
StartDatePhase = new DateTime(2016, 7, 22, 8, 0, 0),
EndDatePhase = new DateTime(2016, 7, 22, 9, 30, 0)
},
new PlanetPhase
{
Name = "Saturn",
StartDatePhase = new DateTime(2016, 7, 22, 10, 0, 0),
EndDatePhase = new DateTime(2016, 7, 22, 11, 0, 0)
},
new PlanetPhase
{
Name = "Saturn",
StartDatePhase = new DateTime(2016, 7, 22, 11, 20, 0),
EndDatePhase = new DateTime(2016, 7, 22, 12, 30, 0)
},
new PlanetPhase
{
Name = "Saturn",
StartDatePhase = new DateTime(2016, 7, 22, 14, 0, 0),
EndDatePhase = new DateTime(2016, 7, 22, 16, 0, 0)
},
new PlanetPhase
{
Name = "Saturn",
StartDatePhase = new DateTime(2016, 7, 22, 18, 30, 0),
EndDatePhase = new DateTime(2016, 7, 22, 19, 30, 0)
},
new PlanetPhase
{
Name = "Saturn",
StartDatePhase = new DateTime(2016, 7, 22, 19, 45, 0),
EndDatePhase = new DateTime(2016, 7, 22, 21, 0, 0)
},
};
PlanetPhase previous = null;
int groupon = 0;
var results = galaxySector.GroupBy(p => p.Name)
.Select(grp => new
{
PlanetName = grp.Key,
Phases = grp.OrderBy(p => p.StartDatePhase)
.Select(p =>
{
if (previous != null
&& p.StartDatePhase - previous.EndDatePhase > TimeSpan.FromMinutes(30))
{
groupon++;
}
previous = p;
return new
{
groupOn = groupon,
p.StartDatePhase,
p.EndDatePhase
};
})
.GroupBy(x => x.groupOn)
.Select(g => new
{
Start = g.Min(x => x.StartDatePhase),
End = g.Max(x => x.EndDatePhase)
})
.ToList()
});
foreach (var r in results)
{
Console.WriteLine(r.PlanetName);
foreach (var p in r.Phases)
Console.WriteLine($"\t{p.Start} - {p.End}");
}
那会输出
Saturn
7/22/2016 8:00:00 AM - 7/22/2016 12:30:00 PM
7/22/2016 2:00:00 PM - 7/22/2016 4:00:00 PM
7/22/2016 6:30:00 PM - 7/22/2016 9:00:00 PM
如果你用 yield return
:
将多个循环打包到一个可枚举返回的方法中,这样的分组可以非常方便地完成
private static readonly TimeSpan HalfHour = TimeSpan.Parse("0:30");
private static IEnumerable<Schedule> Group(IList<GalaxySector> all) {
// Protect from division by zero
if (all.Count == 0) {
yield break;
}
// Find initial location
var pos = 0;
while (pos < all.Count) {
var prior = (pos + all.Count - 1) % all.Count;
if (all[prior].End+HalfHour >= all[pos].Begin) {
pos++;
} else {
break;
}
}
// Protect from wrap-around when all items belong to a single window
pos = pos % all.Count;
// Start grouping items together
var stop = pos;
do {
var start = pos;
var next = (pos+1) % all.Count;
while (next != stop && all[pos].End+HalfHour >= all[next].Begin) {
pos = next;
next = (pos+1) % all.Count;
}
yield return new Schedule {Begin = all[start].Begin, End = all[pos].End};
pos = next;
} while (pos != stop);
}
上面的代码在午夜 (demo) 执行 "wrap around"。
方法比较简单:第一个循环通过回溯一步找到开始迭代的位置,这样回绕后的schedule是连续的。第二个循环记住起始位置,一次前进一步,检查 windows 是否相隔半小时。一旦找到足够大的中断,或者当我们再次到达起点时,第二个循环停止。
如果您不想使用 yield return
,您可以将其添加到 List<Schedule>
。
var all = new GalaxySector[] {
new GalaxySector {Begin=TimeSpan.Parse("0:15"), End=TimeSpan.Parse("2:30")}
, new GalaxySector {Begin=TimeSpan.Parse("2:45"), End=TimeSpan.Parse("3:30")}
, new GalaxySector {Begin=TimeSpan.Parse("8:00"), End=TimeSpan.Parse("9:30")}
, new GalaxySector {Begin=TimeSpan.Parse("10:00"), End=TimeSpan.Parse("11:00")}
, new GalaxySector {Begin=TimeSpan.Parse("11:20"), End=TimeSpan.Parse("12:30")}
, new GalaxySector {Begin=TimeSpan.Parse("14:00"), End=TimeSpan.Parse("16:00")}
, new GalaxySector {Begin=TimeSpan.Parse("18:30"), End=TimeSpan.Parse("19:30")}
, new GalaxySector {Begin=TimeSpan.Parse("19:45"), End=TimeSpan.Parse("21:00")}
, new GalaxySector {Begin=TimeSpan.Parse("22:00"), End=TimeSpan.Parse("23:50")}
};
foreach (var sched in Group(all)) {
Console.WriteLine("{0}..{1}", sched.Begin, sched.End);
}
输出:
08:00:00..12:30:00
14:00:00..16:00:00
18:30:00..21:00:00
22:00:00..03:30:00
我有一个对象集合,我需要对其进行迭代和处理。到目前为止似乎很容易。但是,我有一些条件使它变得相当复杂。
这是一些信息:
该集合包含一堆 "Planet" 具有行星相位时间的对象。
如果两个阶段之间的时间跨度小于或等于 30 分钟,则行星观看时间将合并为块。
例如这里有6个相位时间:
- 第 1 阶段:8:00am - 9:30am
- 第 2 阶段:10:00am - 11:00am
- 第 3 阶段:11:20am - 12:30pm
- 第 4 阶段:2:00pm - 4:00pm
- 第 5 阶段:6:30pm - 7:30pm
- 第 6 阶段:7:45pm - 9:00pm
根据上面的数据,我们有以下块:
- 第 1 阶段到第 3 阶段:一个连续观看块
- 第 4 阶段:单独的观看区
- 第 5 阶段和第 6 阶段:一个连续观看块
数学:
- (第2阶段开始时间)-(第1阶段结束时间)= 30分钟
- (第三阶段开始时间) - (第二阶段结束时间) = 20分钟
- (第 4 阶段开始时间)-(第 3 阶段结束时间)= 90 分钟
- (第5阶段开始时间)-(第4阶段结束时间)= 150分钟
- (第6阶段开始时间) - (第5阶段结束时间) = 15分钟
到目前为止我失败的尝试:
int i = 0;
bool continueBlocking = false;
foreach (var p in GalaxySector) //IEnumerable
{
//ensure that dates are not null
if (p.StartDatePhase != null || p.EndDatePhase != null) {
if (continueBlocking) {
string planetName = p.Name;
string planetCatalogId = p.CatalogId;
datetime? StartPhase = p.StartDatePhase.Value;
datetime? EndPhase = p.EndDatePhase.Value;
} else {
string planetName = p.Name;
string planetCatalogId = p.CatalogId;
datetime? StartPhase = p.StartDatePhase.Value;
datetime? EndPhase = p.EndDatePhase.Value;
}
if (i < 2) {
continue;
}
TimeSpan? spanBetweenSections = StartPhase - EndPhase;
if ( spanBetweenSections.Value.TotalMinues <= 30) {
continueBlocking = true;
continue;
} else {
CreateSchedule(planetName, planetCatalogId, StartPhase, EndPhase);
continueBlocking = false;
}
}
i++;
}
我在这个愚蠢的循环上花了好几个小时,我认为另一双眼睛会很好。
它 feels/looks 太复杂、太过时、太混乱。有 better/modern 的方法吗?
谢谢!
假设这些是日期而不仅仅是一天中的时间,您可以执行以下操作
var galaxySector = new List<PlanetPhase>
{
new PlanetPhase
{
Name = "Saturn",
StartDatePhase = new DateTime(2016, 7, 22, 8, 0, 0),
EndDatePhase = new DateTime(2016, 7, 22, 9, 30, 0)
},
new PlanetPhase
{
Name = "Saturn",
StartDatePhase = new DateTime(2016, 7, 22, 10, 0, 0),
EndDatePhase = new DateTime(2016, 7, 22, 11, 0, 0)
},
new PlanetPhase
{
Name = "Saturn",
StartDatePhase = new DateTime(2016, 7, 22, 11, 20, 0),
EndDatePhase = new DateTime(2016, 7, 22, 12, 30, 0)
},
new PlanetPhase
{
Name = "Saturn",
StartDatePhase = new DateTime(2016, 7, 22, 14, 0, 0),
EndDatePhase = new DateTime(2016, 7, 22, 16, 0, 0)
},
new PlanetPhase
{
Name = "Saturn",
StartDatePhase = new DateTime(2016, 7, 22, 18, 30, 0),
EndDatePhase = new DateTime(2016, 7, 22, 19, 30, 0)
},
new PlanetPhase
{
Name = "Saturn",
StartDatePhase = new DateTime(2016, 7, 22, 19, 45, 0),
EndDatePhase = new DateTime(2016, 7, 22, 21, 0, 0)
},
};
PlanetPhase previous = null;
int groupon = 0;
var results = galaxySector.GroupBy(p => p.Name)
.Select(grp => new
{
PlanetName = grp.Key,
Phases = grp.OrderBy(p => p.StartDatePhase)
.Select(p =>
{
if (previous != null
&& p.StartDatePhase - previous.EndDatePhase > TimeSpan.FromMinutes(30))
{
groupon++;
}
previous = p;
return new
{
groupOn = groupon,
p.StartDatePhase,
p.EndDatePhase
};
})
.GroupBy(x => x.groupOn)
.Select(g => new
{
Start = g.Min(x => x.StartDatePhase),
End = g.Max(x => x.EndDatePhase)
})
.ToList()
});
foreach (var r in results)
{
Console.WriteLine(r.PlanetName);
foreach (var p in r.Phases)
Console.WriteLine($"\t{p.Start} - {p.End}");
}
那会输出
Saturn
7/22/2016 8:00:00 AM - 7/22/2016 12:30:00 PM
7/22/2016 2:00:00 PM - 7/22/2016 4:00:00 PM
7/22/2016 6:30:00 PM - 7/22/2016 9:00:00 PM
如果你用 yield return
:
private static readonly TimeSpan HalfHour = TimeSpan.Parse("0:30");
private static IEnumerable<Schedule> Group(IList<GalaxySector> all) {
// Protect from division by zero
if (all.Count == 0) {
yield break;
}
// Find initial location
var pos = 0;
while (pos < all.Count) {
var prior = (pos + all.Count - 1) % all.Count;
if (all[prior].End+HalfHour >= all[pos].Begin) {
pos++;
} else {
break;
}
}
// Protect from wrap-around when all items belong to a single window
pos = pos % all.Count;
// Start grouping items together
var stop = pos;
do {
var start = pos;
var next = (pos+1) % all.Count;
while (next != stop && all[pos].End+HalfHour >= all[next].Begin) {
pos = next;
next = (pos+1) % all.Count;
}
yield return new Schedule {Begin = all[start].Begin, End = all[pos].End};
pos = next;
} while (pos != stop);
}
上面的代码在午夜 (demo) 执行 "wrap around"。
方法比较简单:第一个循环通过回溯一步找到开始迭代的位置,这样回绕后的schedule是连续的。第二个循环记住起始位置,一次前进一步,检查 windows 是否相隔半小时。一旦找到足够大的中断,或者当我们再次到达起点时,第二个循环停止。
如果您不想使用 yield return
,您可以将其添加到 List<Schedule>
。
var all = new GalaxySector[] {
new GalaxySector {Begin=TimeSpan.Parse("0:15"), End=TimeSpan.Parse("2:30")}
, new GalaxySector {Begin=TimeSpan.Parse("2:45"), End=TimeSpan.Parse("3:30")}
, new GalaxySector {Begin=TimeSpan.Parse("8:00"), End=TimeSpan.Parse("9:30")}
, new GalaxySector {Begin=TimeSpan.Parse("10:00"), End=TimeSpan.Parse("11:00")}
, new GalaxySector {Begin=TimeSpan.Parse("11:20"), End=TimeSpan.Parse("12:30")}
, new GalaxySector {Begin=TimeSpan.Parse("14:00"), End=TimeSpan.Parse("16:00")}
, new GalaxySector {Begin=TimeSpan.Parse("18:30"), End=TimeSpan.Parse("19:30")}
, new GalaxySector {Begin=TimeSpan.Parse("19:45"), End=TimeSpan.Parse("21:00")}
, new GalaxySector {Begin=TimeSpan.Parse("22:00"), End=TimeSpan.Parse("23:50")}
};
foreach (var sched in Group(all)) {
Console.WriteLine("{0}..{1}", sched.Begin, sched.End);
}
输出:
08:00:00..12:30:00
14:00:00..16:00:00
18:30:00..21:00:00
22:00:00..03:30:00