简体   繁体   English

如何创建一个特殊的“组合”枚举器

[英]How to create a special 'combining' enumerator

I have (or will have) a class that returns a sequence of dates (based around a Recurrence pattern). 我有(或将要)一个返回日期序列的类(基于重复模式)。 The sequence may have a finite end (# results or an end date) or be infinite. 序列可能具有有限的结束(#结果或结束日期)或无限。

What I want to do is write a class that will take a list of such enumerators (plus a Start Date) and 'combine' their sequences into one enumerable output sequence ordered by date. 我想要做的是编写一个类,它将获取这些枚举器的列表(加上一个开始日期),并将它们的序列“组合”成一个按日期排序的可枚举输出序列。 It would have to deal with source enumerations ending (or not even starting) and also with multiple source enumerations producing the same Date. 它必须处理结束(或甚至不启动)的源枚举,以及生成相同Date的多个源枚举。

So my question is how can I best achieve this? 所以我的问题是如何才能最好地实现这一目标?

More information (if it helps): 更多信息(如果有帮助):
eg If I have RecurrencePatterns of:- 例如,如果我有RecurrencePatterns: -
(1) "1st of each month, ending 4th July 2017"; (1)“每月1日,2017年7月4日结束”;
(2) "End of each quarter, 6 occurrences only" (2)“每季度结束,仅发生6次”
(3) "Last day of the Year" (3)“年度最后一天”
and I start with 1st Jan 2017, I want the output to be:- 我从2017年1月1日开始,我希望输出为: -

1st Jan 2017(1), 2017年1月1日(1),
1st Feb 2017 (1), 2017年2月1日(1),
1st March 2017 (1), 2017年3月1日(1),
31st March 2017 (2), 2017年3月31日(2),
1st April 2017 (1), 2017年4月1日(1),
1st May 2017 (1), 2017年5月1日(1),
1st June 2017 (1), 2017年6月1日(1),
30th June 2017 (2) 2017年6月30日(2)
1st July 2017 (1), [(1) - last enumeration because next is after its end date] 2017年7月1日(1),[(1) - 最后一次列举因为下一次是在结束日期之后]
30th September 2017 (2), 2017年9月30日(2),
31st December 2017 (2, 3), [two events with same date] 2017年12月31日(2,3),[同日期的两个活动]
31st March 2018 (2), 2018年3月31日(2),
30th June 2018 (2) [(2) - last enumeration because it only produces 6] 2018年6月30日(2)[(2) - 最后一次列举,因为它只产生6]
31st December 2018 (3), 2018年12月31日(3),
31st December 2019 (3), 2019年12月31日(3),
31st December 2020 (3), etc. 2020年12月31日(3)等

The actual output of the Recurrence pattern class will probably be some sort of EventInfo class containing the Source and the Date. Recurrence模式类的实际输出可能是某种包含Source和Date的EventInfo类。 The output of the 'combining' enumerator will be similar but should multiple Recurrence EventInfo have the same date, it would output a single EventInfo with a single Date and a list of sources which return that date. '组合'枚举器的输出将类似,但是如果多个Recurrence EventInfo具有相同的日期,它将输出具有单个Date的单个EventInfo和返回该日期的源列表。

You should use SortedMerge in MoreLINQ, if you want duplicates to be returned, or OrderedMerge if duplicates are not desired. 您应该使用SortedMerge在MoreLINQ,如果你想返回重复,或OrderedMerge如果重复不期望的。


Here's how you might write it, if you didn't want to install a NuGet package, or none of the overrides are suitable for your needs. 如果您不想安装NuGet包,或者没有任何覆盖适合您的需要,您可以按照以下方式编写它。

static IEnumerable<DateTime> CombineDateTimes(IEnumerable<IEnumerable<DateTime>> list) {
    // keep track of which enumerators are still available
    var eligible = list.Select(l => l.GetEnumerator()).Where(l => l.MoveNext()).ToList();

    while (eligible.Any()) {
        // find the lowest enumerator
        IEnumerator<DateTime> min = eligible.First();
        foreach (var l in eligible.Skip(1)) {
            if (l.Current < min.Current) {
                min = l;
            }
        }

        // here is our lowest
        yield return min.Current;

        if (!min.MoveNext()) {
            // if the enumerator ends,
            // remove it from the list of eligible enumerators
            eligible = eligible.Remove(min);
        }
    }
}

Something like this, I think: 我认为这样的事情:

public static IEnumerable<DateTime> SortDates(IEnumerable<IEnumerable<DateTime>> iidt)
{
    var enumerators = iidt.Select(iedt => iedt.GetEnumerator())
                        .Where(e => e.MoveNext())
                        .ToList();

    while (enumerators.Any())
    {
        var lowest = enumerators.OrderBy(e => e.Current).First();

        yield return lowest.Current;

        if (!lowest.MoveNext())
        {
            enumerators.Remove(lowest);
        }
    }
}

Thanks to Answers from Jacob and Ed, I was able to come up with the following code which someone may find useful: 感谢雅各布和埃德的答案,我能够提出以下代码,有人可能会觉得有用:

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;

using NUnit.Framework;

namespace TIPS.UnitTest
{
public class EventInfo
{
    public DateTime Date;
    public EventSource EventSource;
    public List<EventSource> AdditionalEventSources; // Left null if no additional sources
}

public abstract class EventSource
{
    public string Name
    {
        get { return GetType().Name; }
    }

    public abstract IEnumerable<DateTime> RecurringDates();
}

public interface IEventInfoGenerator
{
    IEnumerable<EventInfo> GenerateEvents(List<EventSource> eventSources);
}

public class MyAnswerEventInfoGenerator: IEventInfoGenerator
{
    public IEnumerable<EventInfo> GenerateEvents(List<EventSource> eventSources)
    {
        // Combine the Event Sources and their DateEnumerators ignoring any without Dates to return
        var sourceEnumerators = eventSources
            .Select(es =>
                        new
                            {
                                Source = es,
                                DateEnumerator = es.RecurringDates().GetEnumerator()
                            })
            .Where(e => e.DateEnumerator.MoveNext())
            .ToList();

        // Keep going until there is nothing left
        while (sourceEnumerators.Any())
        {
            // Find the earliest date
            var earliestSource = sourceEnumerators.OrderBy(se => se.DateEnumerator.Current).First();

            // Prepare the EventInfo
            var eventInfo = new EventInfo
                                {
                                    Date = earliestSource.DateEnumerator.Current,
                                    EventSource = earliestSource.Source
                                };

            // Remove the EventSource if it has no more Dates
            if (!earliestSource.DateEnumerator.MoveNext())
            {
                sourceEnumerators.Remove(earliestSource);
            }

            // Quick check to see if there are other EventSources with the same date (no need to create a list if not necessary
            if (sourceEnumerators.Any(se => se != earliestSource && se.DateEnumerator.Current == eventInfo.Date))
            {
                // Yes, there are so create a list for them
                eventInfo.AdditionalEventSources = new List<EventSource>();

                // Go through each 
                foreach (var additionalSourceEnumerator in sourceEnumerators.Where(se => se != earliestSource && se.DateEnumerator.Current == eventInfo.Date).ToArray())
                {
                    // Add it to the EventInfo list
                    eventInfo.AdditionalEventSources.Add(additionalSourceEnumerator.Source);

                    // And remove them if they are spent
                    if (!additionalSourceEnumerator.DateEnumerator.MoveNext())
                    {
                        sourceEnumerators.Remove(additionalSourceEnumerator);
                    }
                }
            }

            yield return eventInfo;
        }
    }
}


[TestFixture]
public class RecurrenceTests
{
    static IEnumerable<EventSource> CreateTestEventSources()
    {
        yield return new EventSource1();
        yield return new EventSource2();
        yield return new EventSource3();
        yield return new EmptyEventSource();
    }

    static void TestStackoverAnswer(IEventInfoGenerator answer)
    {
        var testEventSources = CreateTestEventSources().ToList();

        foreach (var eventInfo in answer.GenerateEvents(testEventSources))
        {
            Debug.Write($"{eventInfo.Date} - {eventInfo.EventSource.Name}");

            if (eventInfo.AdditionalEventSources != null)
            {
                Debug.Write(", " + string.Join(", ", eventInfo.AdditionalEventSources.Select(ev => ev.Name)));
            }

            Debug.WriteLine(string.Empty);
        }
    }

    [Test]
    public void TestMyGo()
    {
        TestStackoverAnswer(new MyAnswerEventInfoGenerator());
    }
}

public class EventSource1: EventSource
{
    public override IEnumerable<DateTime> RecurringDates()
    {
        yield return new DateTime(2017, 1, 1);
        yield return new DateTime(2017, 2, 1);
        yield return new DateTime(2017, 3, 1);
        yield return new DateTime(2017, 4, 1);
        yield return new DateTime(2017, 5, 1);
        yield return new DateTime(2017, 6, 1);
        yield return new DateTime(2017, 7, 1);
    }
}

public class EventSource2: EventSource
{
    public override IEnumerable<DateTime> RecurringDates()
    {
        yield return new DateTime(2017, 3, 31);
        yield return new DateTime(2017, 6, 30);
        yield return new DateTime(2017, 9, 30);
        yield return new DateTime(2017, 12, 31);
    }
}

public class EventSource3: EventSource
{
    public override IEnumerable<DateTime> RecurringDates()
    {
        yield return new DateTime(2017, 12, 31);
        yield return new DateTime(2018, 12, 31);
        yield return new DateTime(2019, 12, 31);
    }
}

public class EmptyEventSource: EventSource
{
    public override IEnumerable<DateTime> RecurringDates()
    {
        yield break;
    }
}

} }

The output of the Test looks like this:- 测试的输出如下: -

01/01/2017 00:00:00 - EventSource1
01/02/2017 00:00:00 - EventSource1
01/03/2017 00:00:00 - EventSource1
31/03/2017 00:00:00 - EventSource2
01/04/2017 00:00:00 - EventSource1
01/05/2017 00:00:00 - EventSource1
01/06/2017 00:00:00 - EventSource1
30/06/2017 00:00:00 - EventSource2
01/07/2017 00:00:00 - EventSource1
30/09/2017 00:00:00 - EventSource2
31/12/2017 00:00:00 - EventSource2, EventSource3
31/12/2018 00:00:00 - EventSource3
31/12/2019 00:00:00 - EventSource3

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM