开发者

Find the Missing Months

开发者 https://www.devze.com 2023-02-04 21:54 出处:网络
I\'ve got my own way of doing this but I\'m not convinced its the best, in C# Given a List<DateTime&g开发者_Python百科t;, a DateTime startDate and an DateTime endDate. How would you return a newLi

I've got my own way of doing this but I'm not convinced its the best, in C#

Given a List<DateTime&g开发者_Python百科t;, a DateTime startDate and an DateTime endDate. How would you return a new List<DateTime>for every month between startDate and endDate that is not included within the original List<DateTime> inclusive of the startDate and endDate.

Dates are not guarnteed to be the start of the month, could be any date within the month.

startDate and endDate could span multiple years.

The returned list should contain the first day of every month that is missing.

Thanks, and I hope it makes sense.


var list = new List<DateTime>
    {
        new DateTime(1231223423433132),
        new DateTime(13223123132),
        new DateTime(12333123132),
        new DateTime(123345123132),
        DateTime.Now,
        new DateTime(5634534553)
    };

var allYearMonthes = list.Select(o => 
                             Eumerable.Range(1, 12)
                                 .Select(q => new { o.Year, Month = q }))
                          .SelectMany(o => o);

var enumerable = allYearMonthes.Except(list.Select(o => new { o.Year, o.Month }));

var dateTimes = enumerable.Select(o => new DateTime(o.Year, o.Month, 1));

EDIT: for those who interested in probably complete solution:

DateTime StartDate = DateTime.Now, EndDate = DateTime.Now.AddYears(5).AddMonths(2);
var allYearMonthes = Enumerable.Range(StartDate.Year, EndDate.Year - StartDate.Year -1)
                               .Select(o => Enumerable.Range(1, 12)
                               .Select(q => new { Year = o, Month = q }))
                               .SelectMany(o => o);

var enumerable = allYearMonthes.Except(list.Select(o => new { o.Year, o.Month }));
var dateTimes = enumerable.Select(o => new DateTime(o.Year, o.Month, 1));


Well, assuming the same month in different years is considered different:

    private List<DateTime> GetUnincludedMonths(DateTime startDate, DateTime endDate,
                                               IEnumerable<DateTime> dates)
    {
        var allMonths = new HashSet<Tuple<int, int>>(); //month, year
        DateTime date = startDate;
        while (date <= endDate)
        {
            allMonths.Add(Tuple.Create(date.Month, date.Year));
            date = date.AddMonths(1);
        }
        allMonths.Add(Tuple.Create(endDate.Month, endDate.Year));

        allMonths.ExceptWith(dates.Select(dt => Tuple.Create(dt.Month, dt.Year)));
        return allMonths.Select(t => new DateTime(t.Item2, t.Item1, 1)).ToList();
    }


Here's what I would do:

static IEnumerable<DateTime> GetMissingMonths(IEnumerable<DateTime> currentDates, DateTime startDate, DateTime endDate)
{
    var yearMonths = new HashSet<Tuple<int, int>>(currentDates.Select(d => Tuple.Create(d.Year, d.Month)));
    DateTime current = new DateTime(startDate.Year, startDate.Month, 1);
    if (current < startDate)
        current = current.AddMonths(1);
    while (current <= endDate)
    {
        if (!yearMonths.Contains(Tuple.Create(current.Year, current.Month)))
        {
            yield return current;
        }
        current = current.AddMonths(1);
    }
}

EDIT: if you can't use Tuple, you can use an anonymous type instead, with a helper method to create the HashSet:

static IEnumerable<DateTime> GetMissingMonths(IEnumerable<DateTime> currentDates, DateTime startDate, DateTime endDate)
{
    var yearMonths = MakeHashSet(currentDates.Select(d => new { d.Year, d.Month }));
    DateTime current = new DateTime(startDate.Year, startDate.Month, 1);
    if (current < startDate)
        current = current.AddMonths(1);
    while (current <= endDate)
    {
        if (!yearMonths.Contains(new { current.Year, current.Month }))
        {
            yield return current;
        }
        current = current.AddMonths(1);
    }
}

static HashSet<T> MakeHashSet<T>(IEnumerable<T> source)
{
    return new HashSet<T>(source);
}

The MakeHashSet method allows you to use type inference to create a HashSet<T> when T is an anonymous type.


LINQPad-working solution:

void Main()
{
    var dates = new List<DateTime>
    {
        new DateTime(2011, 1, 1),
        new DateTime(2011, 3, 5),
        new DateTime(2011, 7, 28),
    };
    var startDate = new DateTime(2011, 1, 1);
    var endDate = new DateTime(2012, 12, 31);
    var existingMonths =
        (from dt in dates
         select dt.Year * 12 + dt.Month - 1).Distinct().ToArray();
    var missingMonths =
        from ym in Enumerable.Range(
            startDate.Year * 12 + startDate.Month - 1,
            (endDate.Year * 12 + endDate.Month) - (startDate.Year * 12 + startDate.Month) + 1)
        where !existingMonths.Contains(ym)
        select new DateTime(ym / 12, ym % 12 + 1, 1);
    missingMonths.Dump();
}


public IEnumerable<DateTime> GetMissingMonths(
  DateTime startDate,
  DateTime endDate,
  IEnumerable<DateTime> source)
{
  IEnumerable<DateTime> sourceMonths =
    source.Select(x => new DateTime(x.Year, x.Month, 1))
          .ToList()
          .Distinct();
  return MonthsBetweenInclusive(startDate, endDate).Except(sourceMonths);
}

public IEnumerable<DateTime> MonthsBetweenInclusive(
  DateTime startDate,
  DateTime endDate)
{
  DateTime currentMonth = new DateTime(startDate.Year, startDate.Month, 1);
  DateTime endMonth = new DateTime(endDate.Year, endDate.Month, 1);

  while(currentMonth <= endMonth)
  {
    yield return currentMonth;
    currentMonth = currentMonth.AddMonths(1);
  }
}


static void Main(string[] args)
        {
            var days = (new string[] { "3/23/2000", "7/3/2004", "1/3/2004", "3/1/2011" })
                        .Select(a => Convert.ToDateTime(a));

            days = days.Select(a => a.AddDays(1 - (a.Day))).Distinct();
            days = days.OrderBy(a => a);

            var missingMonths = GetMissingMonths(days).ToList();
        }

        private static IEnumerable<DateTime> GetMissingMonths(IEnumerable<DateTime> days)
        {
            DateTime previous = days.First();
            foreach (var current in days.Skip(1))
            {
                int months = (current.Month - previous.Month) + 
                                    12 * (current.Year - previous.Year);
                for (int i = 1; i < months; i++)
                {
                    yield return previous.AddMonths(i);
                }
                previous = current;
            }
        }


Thanks to Jani +1 for his idea. This one is one line of code :)

void Main()
 {

 var list = new List<DateTime>
 {
  new DateTime(2005, 10, 11),
  new DateTime(2009, 3, 4),
  new DateTime(2010, 5, 8),
  new DateTime(2010, 8, 10),
  DateTime.Now,
  new DateTime(2010, 4, 8)
 };

        var result= Enumerable.Range(list.Min (l => l.Year), list.Max (l => l.Year) - list.Min (l => l.Year)).
             SelectMany (e => Enumerable.Range(1, 12).Select (en => new DateTime(e, en, 1))).
             Except(list.Select(o => new DateTime(o.Year, o.Month, 1))).
             Where (o => o.Date > list.Min (l => l.Date) && o.Date < list.Max (l => new DateTime(l.Year, l.Month, 1)));

    }


I'll throw my hat in, because it's fun. and I didn't see anyone putting a DateTime in a regular for loop, which I never get to do, so again... fun.

IEnumerable<DateTime> FindMissingMonths(DateTime startDate, DateTime endDate, IEnumerable<DateTime> inputs)
{
    var allMonths = new List<DateTime>();
    for (DateTime d = startDate; d < endDate; d = d.AddMonths(1))
    {
        allMonths.Add(new DateTime(d.Year, d.Month, 1));
    }
    var usedMonths = (from d in inputs
                        select new DateTime(d.Year, d.Month, 1)).Distinct();
    return allMonths.Except(usedMonths);
}

fixed a bug, tested, works.


    public IList<DateTime> GetMissingMonths(IList<DateTime> currentList, DateTime startDate, DateTime endDate)
    {
        // Create a list for the missing months
        IList<DateTime> missingList = new List<DateTime>();

        // Select a startdate
        DateTime testingDate = startDate;

        // Begin by the month of startDate and ends with the month of endDate
        // month of startDate and endDate included
        while(testingDate <= endDate)
        {
            if (currentList.Count(m => m.Month == testingDate.Month && m.Year == testingDate.Year) == 0)
            {
                missingList.Add(new DateTime(testingDate.Year, testingDate.Month, 1));
            }
            testingDate = testingDate.AddMonths(1);
        }
        return missingList;
    }
0

精彩评论

暂无评论...
验证码 换一张
取 消