具有多个逻辑条件的 foreach 循环

foreach loop with multiple logic conditions

我有一个对象集合,我需要对其进行迭代和处理。到目前为止似乎很容易。但是,我有一些条件使它变得相当复杂。

这是一些信息:

该集合包含一堆 "Planet" 具有行星相位时间的对象。

如果两个阶段之间的时间跨度小于或等于 30 分钟,则行星观看时间将合并为块。

例如这里有6个相位时间:

根据上面的数据,我们有以下块:

数学:

到目前为止我失败的尝试:

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